package com.cloudfun.campusshare.service.impl;

import com.cloudfun.campusshare.common.constant.RefundReview;
import com.cloudfun.campusshare.common.constant.PayPlatform;
import com.cloudfun.campusshare.common.constant.OrderStatus;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.cloudfun.campuscontract.RemoteOrderBlockChainService;
import com.cloudfun.campuscontract.dto.OrderDTO;
import com.cloudfun.campusshare.common.WechatUserHolder;
import com.cloudfun.campusshare.common.constant.*;
import com.cloudfun.campusshare.common.constant.miniprogram.WeChatTradeState;
import com.cloudfun.campusshare.common.exception.BusinessException;
import com.cloudfun.campusshare.common.model.dto.*;
import com.cloudfun.campusshare.common.model.dto.kjt.KjtPaymentNotifyDTO;
import com.cloudfun.campusshare.common.model.vo.*;
import com.cloudfun.campusshare.common.response.BaseResult;
import com.cloudfun.campusshare.common.response.BaseResultFactory;
import com.cloudfun.campusshare.common.response.PageResult;
import com.cloudfun.campusshare.common.security.AuthenticationHolder;
import com.cloudfun.campusshare.common.security.UserAuthentication;
import com.cloudfun.campusshare.configuration.property.HsqProperty;
import com.cloudfun.campusshare.configuration.property.WeChatProperties;
import com.cloudfun.campusshare.entity.*;
import com.cloudfun.campusshare.repo.*;
import com.cloudfun.campusshare.sender.DelayMessage;
import com.cloudfun.campusshare.sender.DelayTaskMessageSender;
import com.cloudfun.campusshare.service.*;
import com.cloudfun.campusshare.util.*;
import com.cloudfun.campusshare.util.hsq.IPUtil;
import com.cloudfun.campusshare.util.tencent.wechat.WeChatUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.task.TaskExecutor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Predicate;
import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * Created by Huqin on 2019/12/11
 */
@Slf4j
@Service
@Transactional
public class DeviceOrderServiceImpl implements DeviceOrderService {

    @Value("${wechat.appId}")
    private String appId;

    @Value("${wechat.mchId}")
    private String mchId;

    @Value("${wechat.pay.callback}")
    private String payCallBack;

    @Value("${wechat.pay.withoutcode.callback}")
    private String payWithoutCodeCallBack;

    @Value("${wechat.self.ip}")
    private String selfIp;

    @Autowired
    private OrderEntityRepo orderEntityRepo;

    @Autowired
    private DeviceService deviceService;

    @Autowired
    private DeviceEntityRepo deviceEntityRepo;

    @Resource
    private UserEntityRepo userEntityRepo;

    @Autowired
    private WeChatPaymentService weChatPaymentService;

    @PersistenceContext
    private EntityManager entityManager;

    @Reference(timeout = 10_000)
    private RemoteOrderBlockChainService remoteOrderBlockChainService;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private TaskExecutor taskExecutor;// spring async 默认的任务执行器

    @Autowired
    private OrderNoGenUtil orderNoGenUtil;

    @Autowired
    private DeviceModeService deviceModeService;

    @Autowired
    private DeviceMetaModeService deviceMetaModeService;

    @Autowired
    private WeChatService wechatService;

    @Resource
    private WeChatContractRepo weChatContractRepo;

    @Autowired
    private UserAuthService userAuthService;

    @Autowired
    private DevicePropertyService devicePropertyService;

    @Autowired
    private BillService billService;

    @Autowired
    private DeviceOpServiceDelegate deviceOpService;

    @Autowired
    private WeChatProperties weChatProperties;

    @Autowired
    private DeviceAlarmRepo deviceAlarmRepo;

    @Autowired
    private UserRelService userRelService;

    @Resource
    private WashPointConcretePositionEntityRepo washPointConcretePositionEntityRepo;

    @Autowired
    private UserCouponRepo userCouponRepo;

    @Autowired
    private RebatePromotionRepo rebatePromotionRepo;

    @Autowired
    private DiscountPromotionRepo discountPromotionRepo;

    @Autowired
    private DelayTaskMessageSender delayTaskMessageSender;
    @Resource
    private WashPointEntityRepo washPointEntityRepo;
    @Autowired
    private HsqService hsqService;
    @Autowired
    private HsqProperty hsqProperty;

    @Autowired
    private KjtPaymentService kjtPaymentService;

    @Autowired
    private MerchantAccountService merchantAccountService;

    @Value("${wechat.official.appId}")
    private String weChatOfficialAppId;

    @Value("${alipay.appId}")
    private String alipayAppId;

    @Autowired
    private List<PayService> payServiceList;

    @Resource
    private BusinessOrderRelationEntityRepo businessOrderRelationEntityRepo;

    @Override
    public BaseResult create(DeviceOrderVO deviceOrderVO) {
        DeviceEntity deviceEntity = deviceService.getDeviceWithAuthByImei(deviceOrderVO.getImei());
        OrderEntity orderEntity = OrderEntity.builder()
                // 商户用户Id
                .userId(AuthenticationHolder.getUserId())
                // 商户客户信息
                .customerName(deviceOrderVO.getUsername())
                .customerPhone(deviceOrderVO.getUserPhone())
                // 设备
                .deviceId(deviceEntity.getId())
                .orderType(deviceOrderVO.getOrderType())
                .price(AmountUtil.strAmount2Int(deviceOrderVO.getPrice().toString()))
                .projectId(deviceEntity.getProjectId())
                .tradeTime(String.valueOf(deviceOrderVO.getTradeTime().getTime()))
                .orderNo(orderNoGenUtil.next())
                .orderStatus(OrderStatus.ALREADY_COMPLETE)
                .build();
        OrderEntity entity = orderEntityRepo.save(orderEntity);
        this.orderUpload2BlockChain(orderEntity);
        return BaseResultFactory.produceResult(Code.SUCCESS, entity.getId());
    }

    @Override
    public String orderTotal(String userId) {
        Double total;
        if (AuthenticationHolder.isAdmin() || AuthenticationHolder.getUserAuth().getRoleTypes().contains(RoleType.FINANCIAL_INSTITUTIONS.name())) {
            total = this.orderEntityRepo.orderTotal(userId, OrderStatus.ALREADY_COMPLETE);
        } else {
            total = this.orderEntityRepo.orderTotal(AuthenticationHolder.getUserId(), OrderStatus.ALREADY_COMPLETE);
        }
        if (total == null) {
            total = NumberUtils.DOUBLE_ZERO;
        }
        return AmountUtil.amountRoundStr(total.intValue(), 2);
    }

    @Override
    public String currMonthOrderTotal() {
        if (!AuthenticationHolder.isAdmin()) {
            throw new BusinessException(Code.APP_PERMISSION_DENIED);
        }
        String sql = "select sum(dor.price) from device_order dor where dor.create_time>=:createTimeStart and order_status=:status";
        Query query = entityManager.createNativeQuery(sql);
        query.setParameter("status", OrderStatus.ALREADY_COMPLETE);
        LocalDateTime localDateTime = LocalDateTime.of(LocalDate.now().withDayOfMonth(1), LocalTime.of(0, 0));
        long monthStart = localDateTime.toInstant(OffsetDateTime.now().getOffset()).toEpochMilli();
        query.setParameter("createTimeStart", monthStart + "");
        BigDecimal totalAmount = (BigDecimal) QueryUtil.getSingleResult(query);
        return AmountUtil.amountRoundStr(totalAmount, 0);
    }

    @Override
    public Map<String, String> getUserIdOrderTotalMap(Collection<String> projectIds) {
        if (CollectionUtil.isEmpty(projectIds)) {
            return new HashMap<>();
        }
        String sql = "select dor.user_id,sum(dor.price) from device_order dor where dor.project_id in(:projectIds) group by dor.user_id";
        Query query = entityManager.createNativeQuery(sql);
        query.setParameter("projectIds", projectIds);
        List<Object[]> resultList = query.getResultList();
        if (CollectionUtil.isEmpty(resultList)) {
            return new HashMap<>();
        }
        Function<Object[], String> projectIdMapper = objs -> (String) objs[0];
        Function<Object[], String> sumPriceMapper = objs -> {
            BigDecimal amount = (BigDecimal) objs[1];
            return AmountUtil.amountRoundStr(amount, 0);
        };
        return resultList.stream().collect(Collectors.toMap(projectIdMapper, sumPriceMapper));
    }

    @Override
    public PageResult<ProjectDeviceOrderListVO> searchProjectDeviceOrders(String projectId, Pageable pageable) {
        StringBuilder pageSqlBuilder = new StringBuilder("select do.id,order_no,customer_phone,d.imei,product_type,device_place,trade_time,price,order_status" +
                " from device_order do left join device d on do.device_id=d.id where d.project_id=:projectId limit :start,:end");
        StringBuilder totalNumBuilder = new StringBuilder("select count(*) from device_order do left join device d on do.device_id=d.id" +
                " where d.project_id=:projectId");
        Query pageQuery = entityManager.createNativeQuery(pageSqlBuilder.toString());
        pageQuery.setParameter("projectId", projectId);
        pageQuery.setParameter("start", pageable.getPageSize() * pageable.getPageNumber());
        pageQuery.setParameter("end", pageable.getPageSize());
        List<Object[]> resultList = pageQuery.getResultList();
        List<ProjectDeviceOrderListVO> listVOList = resultList.stream().map(objs -> {
            String id = (String) objs[0];
            String orderNo = (String) objs[1];
            String customerPhone = (String) objs[2];
            String imei = (String) objs[3];
            String productTypeStr = (String) objs[4];
            String devicePlace = (String) objs[5];
            String updateTime = (String) objs[6];
            Integer price = (Integer) objs[7];
            String orderStatusStr = (String) objs[8];
            ProjectDeviceOrderListVO listVO = new ProjectDeviceOrderListVO();
            listVO.setId(id);
            listVO.setOrderNo(orderNo);
            listVO.setCustomerPhone(customerPhone);
            listVO.setImei(imei);
            if (StringUtils.isNotBlank(productTypeStr)) {
                listVO.setProductType(ProductType.valueOf(productTypeStr));
            }
            listVO.setDevicePlace(devicePlace);
            listVO.setUpdateTime(updateTime);
            listVO.setAmount(AmountUtil.amountRoundStr(price, 2));
            if (StringUtils.isNotBlank(orderStatusStr)) {
                listVO.setOrderStatus(OrderStatus.valueOf(orderStatusStr));
            }
            return listVO;
        }).collect(Collectors.toList());

        PageResult<ProjectDeviceOrderListVO> pageResult = new PageResult<>();
        pageResult.setElements(listVOList);

        Query totalNumQuery = entityManager.createNativeQuery(totalNumBuilder.toString());
        totalNumQuery.setParameter("projectId", projectId);
        BigInteger count = (BigInteger) QueryUtil.getSingleResult(totalNumQuery);
        if (null == count) {
            count = BigInteger.ZERO;
        }

        pageResult.setTotalSize(count.longValue());
        return pageResult;
    }

    @Override
    public List<ProjectDeviceOrderExportVO> exportProjectDeviceOrders(String projectId) {
        StringBuilder pageSqlBuilder = new StringBuilder("select do.id,order_no,customer_phone,d.imei,product_type,device_place,trade_time,price,order_status" +
                " from device_order do left join device d on do.device_id=d.id where d.project_id=:projectId");
        Query pageQuery = entityManager.createNativeQuery(pageSqlBuilder.toString());
        pageQuery.setParameter("projectId", projectId);

        List<Object[]> resultList = pageQuery.getResultList();
        return resultList.stream().map(objs -> {
            String id = (String) objs[0];
            String orderNo = (String) objs[1];
            String customerPhone = (String) objs[2];
            String imei = (String) objs[3];
            String productTypeStr = (String) objs[4];
            String devicePlace = (String) objs[5];
            String updateTime = (String) objs[6];
            Integer price = (Integer) objs[7];
            String orderStatusStr = (String) objs[8];
            ProjectDeviceOrderExportVO listVO = new ProjectDeviceOrderExportVO();
            listVO.setId(id);
            listVO.setOrderNo(orderNo);
            listVO.setCustomerPhone(customerPhone);
            listVO.setImei(imei);
            if (StringUtils.isNotBlank(productTypeStr)) {
                listVO.setProductType(ProductType.valueOf(productTypeStr).getDesc());
            }
            listVO.setDevicePlace(devicePlace);
            listVO.setUpdateTime(updateTime);
            listVO.setAmount(AmountUtil.amountRoundStr(price, 2));
            if (StringUtils.isNotBlank(orderStatusStr)) {
                listVO.setOrderStatus(OrderStatus.valueOf(orderStatusStr).getMessage());
            }
            return listVO;
        }).collect(Collectors.toList());
    }

    @Override
    public PageResult<OrderListVO> queryDeviceOrderList(Pageable pageable, String startTime, String endTime, String keyword,
                                                        String deviceType, OrderStatus orderStatus, String projectId) {
        StringBuilder querySql = new StringBuilder("select o.order_no,o.customer_phone,o.create_time,o.price,o.order_status," +
                " d.address,d.product_type,p.name, u.id as user_id,r.type,d.id as did,p.id as project_id," +
                " d.imei,u.enterprise_name as enterprise_name from device_order o LEFT JOIN device d on o.device_id = d.id" +
                " LEFT JOIN project p on d.project_id = p.id left join loan l on p.loan_id = l.id LEFT JOIN user u on d.user_id = u.id" +
                " LEFT JOIN user_role ur on d.user_id = ur.user_id LEFT JOIN role r on ur.role_id = r.id where 1=1 ");
        //金融机构判断项目关联的贷款人是否是自己
        if (AuthenticationHolder.isFinancialInstitutions()) {
            querySql.append(" and l.lender_id = :lenderId");
        }
        //既不是管理员也不是金融机构，判断设备归属
        if (!AuthenticationHolder.isFinancialInstitutions() && !AuthenticationHolder.isAdmin()) {
            querySql.append(" and d.user_id = :userId");
        }
        if (!StringUtils.isBlank(projectId)) {
            querySql.append(" and p.id = :projectId");
        }
        if (!StringUtils.isBlank(startTime)) {
            querySql.append(" and o.create_time >= :startTime");
        }
        if (!StringUtils.isBlank(endTime)) {
            querySql.append(" and o.create_time <= :endTime");
        }
        if (!StringUtils.isBlank(keyword)) {
            querySql.append(" and (o.order_no like :keyword or d.address like :keyword or u.enterprise_name like :keyword" +
                    "  or o.customer_phone like :keyword)");
        }
        if (!StringUtils.isBlank(deviceType)) {
            querySql.append(" and d.product_type = :deviceType");
        }
        if (null != orderStatus) {
            querySql.append(" and o.order_status = :orderStatus");
        }
        querySql.append(" limit :start,:end");
        Query query = entityManager.createNativeQuery(querySql.toString());
        if (AuthenticationHolder.isFinancialInstitutions()) {
            query.setParameter("lenderId", AuthenticationHolder.getUserId());
        }
        if (!StringUtils.isBlank(projectId)) {
            query.setParameter("projectId", projectId);
        }
        if (!AuthenticationHolder.isFinancialInstitutions() && !AuthenticationHolder.isAdmin()) {
            query.setParameter("userId", AuthenticationHolder.getUserId());
        }
        if (!StringUtils.isBlank(startTime)) {
            query.setParameter("startTime", startTime);
        }
        if (!StringUtils.isBlank(endTime)) {
            query.setParameter("endTime", endTime);
        }

        if (!StringUtils.isBlank(keyword)) {
            query.setParameter("keyword", "%" + keyword + "%");
        }
        if (!StringUtils.isBlank(deviceType)) {
            query.setParameter("deviceType", deviceType);
        }
        if (null != orderStatus) {
            query.setParameter("orderStatus", orderStatus);
        }
        query.setParameter("start", pageable.getPageNumber() * pageable.getPageSize());
        query.setParameter("end", pageable.getPageSize());
        List<Object[]> resultList = query.getResultList();

        List<OrderListVO> list = resultList.stream().map(x -> {
            OrderListVO orderListVO = new OrderListVO();
            String orderNo = (String) x[0];
            String userPhone = (String) x[1];
            String createTime = (String) x[2];
            Integer price = (Integer) x[3];
            String _orderStatus = (String) x[4];
            String address = (String) x[5];
            String productType = (String) x[6];
            String projectName = (String) x[7];
            String userId = (String) x[8];
            String roleType = (String) x[9];
            String deviceId = (String) x[10];
            String tmpProjectId = (String) x[11];
            String imei = (String) x[12];
            String enterpriseName = (String) x[13];
            orderListVO.setOrderNo(orderNo);
            orderListVO.setUser(userPhone);
            orderListVO.setOrderCreateTime(createTime);
            orderListVO.setActualMoney(AmountUtil.amountRoundStr(price, 2));
            orderListVO.setOrderStatus(_orderStatus == null ? null : OrderStatus.valueOf(_orderStatus));
            orderListVO.setDeviceAddress(address);
            orderListVO.setDeviceType(productType);
            orderListVO.setProjectName(projectName);
            orderListVO.setUserId(userId);
            orderListVO.setRoleType(roleType);
            orderListVO.setRoleTypeName(RoleType.getDescByName(roleType));
            orderListVO.setDeviceId(deviceId);
            orderListVO.setProjectId(tmpProjectId);
            orderListVO.setDeviceNo(imei);
            orderListVO.setEnterpriseName(enterpriseName);
            return orderListVO;
        }).collect(Collectors.toList());
        Long count = computeTotalCount(startTime, endTime, keyword, deviceType, orderStatus, projectId);
        return new PageResult<>(count, list);
    }

    @Override
    public PageResult<UseOrderListVO> searchUserOrder(Pageable pageable) {
        Page<OrderEntity> pg = searchUserOrder(pageable, Sets.newHashSet(OrderStatus.ALREADY_COMPLETE), null);
        List<OrderEntity> content = pg.getContent();
        return new PageResult<>(pg.getTotalElements(), content.stream().map(x -> {
            UseOrderListVO useOrderListVO = new UseOrderListVO();
            useOrderListVO.setOrderName(x.getOrderName());
            useOrderListVO.setCostTime(x.getCostTime());
            useOrderListVO.setId(x.getId());
            useOrderListVO.setTradeTime(x.getTradeTime());
            useOrderListVO.setPrice(AmountUtil.amountRoundStr(x.getPrice(), 2));
            useOrderListVO.setSettlementAmount(AmountUtil.amountRoundStr(x.getSettlementAmount(), 2));
            return useOrderListVO;
        }).collect(Collectors.toList()));
    }

    @Override
    public UseOrderDetailVO queryUseDetail(String orderId) {
        Optional<OrderEntity> order = orderEntityRepo.findById(orderId);
        if (!order.isPresent()) {
            throw new BusinessException(Code.ERROR, "不存在id为:" + orderId + "的消费记录！");
        }
        OrderEntity entity = order.get();
        UseOrderDetailVO useOrderDetailVO = new UseOrderDetailVO();
        DeviceEntity device = deviceEntityRepo.findById(entity.getDeviceId()).orElseThrow(() -> new BusinessException(Code.NODATA));
        useOrderDetailVO.setId(entity.getId());
        useOrderDetailVO.setOrderName(entity.getOrderName());
        useOrderDetailVO.setOrderNo(entity.getOrderNo());
        useOrderDetailVO.setPrice(AmountUtil.amountRoundStr(entity.getPrice(), 2));
        useOrderDetailVO.setTradeTime(entity.getCreateTime());
        useOrderDetailVO.setCostTime(entity.getCostTime());
        useOrderDetailVO.setServiceTime(entity.getServiceTime());
        useOrderDetailVO.setCompleteTime(entity.getCompleteTime());
        StringBuilder sb = new StringBuilder();
        if (StringUtils.isNotBlank(device.getConcretePositionId())) {
            try {
                WashPointConcretePositionEntity con = washPointConcretePositionEntityRepo.findById(device.getConcretePositionId()).get();
                if (StringUtils.isNotBlank(con.getBuilding())) {
                    sb.append(con.getBuilding()).append("楼");
                }
                if (StringUtils.isNotBlank(con.getFloor())) {
                    sb.append(con.getFloor()).append("层");
                }
                if (StringUtils.isNotBlank(con.getRoom())) {
                    sb.append(con.getRoom()).append("室");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        useOrderDetailVO.setDeviceAddress(sb.toString());
        useOrderDetailVO.setDeviceName(device.getDeviceName());
        useOrderDetailVO.setDeviceType(device.getDeviceType());
        useOrderDetailVO.setProductType(device.getProductType());
        useOrderDetailVO.setOrderStatus(entity.getOrderStatus().name());
        useOrderDetailVO.setWashPointName(entity.getWashPointName());
        useOrderDetailVO.setDiscountAmount(AmountUtil.amountRoundStr(entity.getDiscountPrice(), 2));
        useOrderDetailVO.setSettlementAmount(AmountUtil.amountRoundStr(entity.getSettlementAmount(), 2));
        String washPointId = device.getWashPointId();
        if (StringUtils.isNotBlank(washPointId)) {
            Optional<WashPointEntity> optionalWashPointEntity = washPointEntityRepo.findById(washPointId);
            optionalWashPointEntity.ifPresent(washPointEntity -> {
                useOrderDetailVO.setWashPointDetailAddress(washPointEntity.getDetailAddress());
                useOrderDetailVO.setServicePhone(washPointEntity.getServicePhone());
            });
        }
        return useOrderDetailVO;
    }

    @Override
    public PageResult<ExecutingOrderListVO> queryExecutingOrderList(Pageable pageable) {
        Page<OrderEntity> pg = searchUserOrder(pageable, OrderStatus.PROCESSING_STATUS_SET, null);
        List<OrderEntity> content = pg.getContent();
        return new PageResult<>(pg.getTotalElements(), content.stream().map(x -> {
            ExecutingOrderListVO executingOrderListVO = new ExecutingOrderListVO();
            DeviceEntity device = deviceEntityRepo.findById(x.getDeviceId()).orElseThrow(() -> new BusinessException(Code.NODATA));
            executingOrderListVO.setId(x.getId());
            executingOrderListVO.setDevicePlace(deviceService.parseConcretePosition(device));
            executingOrderListVO.setOrderName(x.getOrderName());
            executingOrderListVO.setOrderNo(x.getOrderNo());
            executingOrderListVO.setImei(device.getImei());
            executingOrderListVO.setStatus(x.getOrderStatus());
            executingOrderListVO.setProductType(x.getProductType());
            if (OrderStatus.IN_ORDER.equals(x.getOrderStatus())) {
                computeWaitQueueInfo(device.getId(), executingOrderListVO, true);
                executingOrderListVO.setIsSubscribe(x.getIsSubscribe());
            }
            executingOrderListVO.setLeftTime(OrderStatus.IN_ORDER.equals(x.getOrderStatus()) ? "预约中" : devicePropertyService.getRealRemainTime(x.getDeviceId()));
            return executingOrderListVO;
        }).collect(Collectors.toList()));
    }

    @Override
    public BaseResult<?> deviceAvailable(DeviceEntity deviceEntity) {
        if (Objects.isNull(deviceEntity)) {
            throw new BusinessException(Code.ERROR, "设备不存在");
        }
        if (!DeviceStatus.NOT_RUNNING.equals(deviceEntity.getStatus())) {
            return BaseResultFactory.produceResult(Code.HAVE_IN_PROGRESS_ORDER, false);
        }
        if (deviceService.deviceLocked(deviceEntity.getId())) {
            return BaseResultFactory.produceResult(Code.ALREADY_IN_USE, false);
        }
        if (!deviceService.deviceAvailable4Customer(deviceEntity)) {
            return BaseResultFactory.produceResult(Code.DEVICE_NOT_AVAILABLE, false);
        }

        if (!deviceService.inServiceTime(deviceEntity.getServiceTimeId())) {
            return BaseResultFactory.produceResult(Code.NOT_IN_SERVICE_TIME, false);
        }
        //判断预约机器数
        if (getUserSubscribeDeviceCount(Objects.isNull(deviceEntity.getProductType()) ? "" : deviceEntity.getProductType().name()) == 2) {
            return BaseResultFactory.produceResult(Code.ORDER_DEVICE_COUNT_OVERFLOW, false);
        }
        return BaseResultFactory.produceResult(Code.SUCCESS, true);
    }

    @Autowired
    private PumpWashRelRepo pumpWashRelRepo;
    @Autowired
    private DeviceModeRepo deviceModeRepo;

    @Override
    @Transactional
    public Map<String, Object> generateOrder(OrderGenerateVO orderGenerateVO) {
        DeviceEntity device = deviceService.getDeviceWithoutAuthByMachineNo(orderGenerateVO.getMachineNo());
        BaseResult<?> deviceAvailable = this.deviceAvailable(device);
        if (deviceAvailable.getCode() != Code.SUCCESS.getCode()) {
            throw new BusinessException(Code.getCodeByCodeInt(deviceAvailable.getCode()), deviceAvailable.getMessage());
        }

        OrderEntity orderEntity;
        List<OrderEntity> subscribeOrders = orderEntityRepo.findByCustomerIdAndDeviceIdAndOrderStatus(AuthenticationHolder.getUserId(), device.getId(), OrderStatus.IN_ORDER);
        if (CollectionUtil.isNotEmpty(subscribeOrders)) {
            //如果该用户已经有预约单
            Collections.sort(subscribeOrders, Comparator.comparing(OrderEntity::getCreateTime));
            orderEntity = subscribeOrders.get(0);
            //如果从预约页面进入，判断当前扫的设备是不是预约的设备,不是的话透出异常
            if (orderGenerateVO.isInvokeByOrderPage() && !orderEntity.getDeviceId().equals(device.getId())) {
                throw new BusinessException(Code.SCAN_WRONG_DEVICE);
            }
            relateMode(orderEntity, orderGenerateVO, device);
            orderEntity.setOrderStatus(OrderStatus.WAIT_PAY);
        } else {
            //判断当前设备是否有未结束的订单
            if (CollectionUtil.isNotEmpty(orderEntityRepo.findAllByDeviceIdAndOrderStatusIn(device.getId(), OrderStatus.PROCESSING_STATUS_SET))) {
                throw new BusinessException(Code.HAVE_IN_PROGRESS_ORDER);
            }

            //判断预约机器数
            if (getUserSubscribeDeviceCount(Objects.isNull(device.getProductType()) ? "" : device.getProductType().name()) == 2) {
                throw new BusinessException(Code.ORDER_DEVICE_COUNT_OVERFLOW);
            }
            orderEntity = assembleOrder(device, OrderStatus.WAIT_PAY, orderGenerateVO);
        }
        OrderEntity save = orderEntityRepo.save(orderEntity);
        final String orderId = save.getId();
        stringRedisTemplate.opsForValue().set(RedisKey.PAY_ORDER_LEFT_TIME_PREFIX + orderId, System.currentTimeMillis() + "", 5, TimeUnit.MINUTES);

        // 超时关闭订单延迟任务
        delayTaskMessageSender.sendDelayMessage(new DelayMessage.Builder().orderId(orderId).deviceId(device.getId())
                .type(DelayMsgType.EXPIRE_PAY_ORDER_CANCEL)
                .userId(AuthenticationHolder.getUserId())
                .consumerType(DelayConsumeAction.OPERATE_LOCK)
                .build(), 5 * 60 * 1000);
        Map<String, Object> map = new HashMap<>();
        map.put("orderId", orderId);
        map.put("leftTime", stringRedisTemplate.getExpire(orderId, TimeUnit.SECONDS));
        return map;
    }

    private void relateMode(OrderEntity orderEntity, OrderGenerateVO orderGenerateVO, DeviceEntity device) {
        DeviceModeDTO merchantMode = deviceModeService.getMerchantMode(orderGenerateVO.getDeviceModeId());
        String price;
        Integer time;
        // 如果是干衣机，价格动态计算，时间为传入的时间
        if (Sets.newHashSet(BaseConstant.GGY8_NAME, BaseConstant.GGY13_5_NAME).contains(device.getDeviceType())) {
            if (null == orderGenerateVO.getDryTime()) {
                throw new BusinessException(Code.PARAMETER_ERROR, "烘干时间不能为空");
            }
            price = NumberUtil.roundStr(NumberUtil.mul(merchantMode.getPrice(), orderGenerateVO.getDryTime().toString()).toString(), 2);
            time = orderGenerateVO.getDryTime();
        } else {
            price = merchantMode.getPrice();
            time = merchantMode.getMetaMode().getTime();
        }
        if (orderGenerateVO.getLiquidPumps() != null && !orderGenerateVO.getLiquidPumps().isEmpty()) {
            PumpWashRel byWashId = pumpWashRelRepo.findByWashImei(device.getImei());
            Optional<DeviceEntity> optional = Optional.ofNullable(deviceEntityRepo.findByImei(byWashId.getPumpImei()));
            List<String> modeIds = orderGenerateVO.getLiquidPumps().stream().filter(liquidPumpVo -> deviceModeRepo.findById(liquidPumpVo.getId()).isPresent())
                    .map(liquidPumpVo -> liquidPumpVo.getId()).collect(Collectors.toList());
            if (optional.isPresent()) {
                Map<String, Object> map = new ConcurrentHashMap<>();
                map.put("liquidPumpModes", modeIds);
                orderEntity.setCommand(JSONUtil.toJsonStr(map));
            }
            List<DeviceModeEntity> collect = orderGenerateVO.getLiquidPumps().stream().filter(liquidPumpVo -> deviceModeRepo.findById(liquidPumpVo.getId()).isPresent())
                    .map(liquidPumpVo -> deviceModeRepo.findById(liquidPumpVo.getId()).get()).collect(Collectors.toList());
            Double aDouble = NumberUtil.add(Double.parseDouble(price), Double.parseDouble(AmountUtil.amountRoundStr(collect.stream().mapToInt(DeviceModeEntity::getPrice).sum(), 2)));
            price = aDouble.toString();
        }
        String productTypeName = merchantMode.getMetaMode().getProductType().getDesc();
        String orderName = productTypeName + "-" + merchantMode.getMetaMode().getName().replace("+", "-");
        orderEntity.setOrderName(orderName);
        orderEntity.setCostTime(time);
        orderEntity.setPrice(AmountUtil.strAmount2Int(price));
        orderEntity.setSettlementAmount(AmountUtil.strAmount2Int(price));
        orderEntity.setUserId(merchantMode.getMerchantId());
        orderEntity.setModeId(orderGenerateVO.getDeviceModeId());
    }

    private OrderEntity assembleOrder(DeviceEntity device, OrderStatus status, OrderGenerateVO orderGenerateVO) {
        String customerId = AuthenticationHolder.getUserId();
        Optional<UserEntity> user = userEntityRepo.findById(customerId);
        UserEntity u = user.get();
        OrderEntity orderEntity = new OrderEntity();
        relateMode(orderEntity, orderGenerateVO, device);
        orderEntity.setDeviceId(device.getId());
        orderEntity.setOrderNo(orderNoGenUtil.next());
        orderEntity.setOrderStatus(status);
        orderEntity.setCustomerId(customerId);
        orderEntity.setCustomerPhone(u.getPhone());
        orderEntity.setCustomerName(u.getUsername());
        orderEntity.setUserId(device.getUserId());
        orderEntity.setWashPointId(device.getWashPointId());
        //设备相关信息
        orderEntity.setProductType(device.getProductType());
        orderEntity.setBelongMerchantName(device.getBelongMerchantName());
        orderEntity.setWashPointName(device.getWashPointName());
        return orderEntity;
    }

    /**
     * 使用优惠活动
     *
     * @param orderPayVO
     * @param orderEntity
     */
    private void applyPromotion(OrderPayVO orderPayVO, OrderEntity orderEntity) {
        String deviceId = orderEntity.getDeviceId();
        DeviceEntity deviceEntity = deviceEntityRepo.findById(deviceId).orElseThrow(() -> new BusinessException(Code.NODATA));
        orderEntity.setDiscountPrice(0);
        Integer originPrice = orderEntity.getPrice();

        // 代金券
        if (StringUtils.isNotBlank(orderPayVO.getUserCouponId())) {
            UserCouponEntity userCouponEntity = userCouponRepo.findById(orderPayVO.getUserCouponId()).orElseThrow(() -> new BusinessException(Code.NODATA));
            UserCouponStatus status = userCouponEntity.getStatus();
            if (status != UserCouponStatus.NOT_USED) {
                throw new BusinessException(Code.ERROR, "代金券已被使用或已过期");
            }
            this.timePeriodCheck(userCouponEntity.getStartTime(), userCouponEntity.getEndTime());
            this.washPointCheck(userCouponEntity.getWashPointId(), deviceEntity.getWashPointId());
            Integer couponAmount = userCouponEntity.getAmount();
            orderEntity.setDiscountPrice(orderEntity.getDiscountPrice() + couponAmount);
        }

        // 折扣
        if (StringUtils.isNotBlank(orderPayVO.getDiscountPromotionId())) {
            DiscountPromotionEntity discountPromotionEntity = discountPromotionRepo.findById(orderPayVO.getDiscountPromotionId())
                    .orElseThrow(() -> new BusinessException(Code.NODATA));
            PromotionEntity discountPromotion = discountPromotionEntity.getPromotion();
            this.promotionCheck(discountPromotion, deviceEntity.getWashPointId(), deviceEntity.getProductType());
            Integer discount = discountPromotionEntity.getDiscount();
            int discountAmount = NumberUtil.round(new BigDecimal(discount).multiply(new BigDecimal(originPrice))
                    .divide(AmountUtil.DEFAULT_MULTIPLIER_BIG_DECIMAL), 0).intValue();
            orderEntity.setDiscountPrice(orderEntity.getDiscountPrice() + discountAmount);
        }

        // 满减
        if (StringUtils.isNotBlank(orderPayVO.getRebatePromotionId())) {
            // 满减
            RebatePromotionEntity rebatePromotionEntity = rebatePromotionRepo.findById(orderPayVO.getRebatePromotionId())
                    .orElseThrow(() -> new BusinessException(Code.NODATA));
            PromotionEntity rebatePromotion = rebatePromotionEntity.getPromotion();
            this.promotionCheck(rebatePromotion, deviceEntity.getWashPointId(), deviceEntity.getProductType());
            int rebateAmount = 0;
            if (orderEntity.getPrice() >= rebatePromotionEntity.getThreshold()) {
                rebateAmount = rebatePromotionEntity.getRebateAmount();
            }
            orderEntity.setDiscountPrice(orderEntity.getDiscountPrice() + rebateAmount);
        }

        // 设置最终金额
        Integer discountPrice = orderEntity.getDiscountPrice() > orderEntity.getPrice() ? orderEntity.getPrice() : orderEntity.getDiscountPrice();
        orderEntity.setDiscountPrice(discountPrice);
        discountPrice = Math.min(discountPrice, originPrice);
        int settlementAmount = originPrice - discountPrice;
        orderEntity.setSettlementAmount(settlementAmount);
    }

    private void promotionCheck(PromotionEntity promotionEntity, String washPoint, ProductType productType) {
        this.timePeriodCheck(promotionEntity.getStartTime(), promotionEntity.getEndTime());
        this.washPointCheck(promotionEntity.getWashPointId(), washPoint);
        this.promotionProductTypeCheck(promotionEntity, productType);
    }

    private void washPointCheck(String actualWashPoint, String expectedWashPoint) {
        if (!actualWashPoint.equals(expectedWashPoint)) {
            throw new BusinessException(Code.ERROR, "洗衣点不适用该活动");
        }
    }

    private void promotionProductTypeCheck(PromotionEntity promotionEntity, ProductType productType) {
        Set<ProductType> productTypes = promotionEntity.getProductTypes().stream().map(PromotionProductType::getProductType).collect(Collectors.toSet());
        if (!productTypes.contains(productType)) {
            throw new BusinessException(Code.ERROR, "设备类型不适用该活动");
        }
    }

    private void timePeriodCheck(LocalDateTime startTime, LocalDateTime endTime) {
        LocalDateTime now = LocalDateTime.now();
        if (startTime.isAfter(now) || endTime.isBefore(now)) {
            throw new BusinessException(Code.ERROR, "活动为开始或已过期");
        }
    }

    @Override
    public void giveUpOrder(String orderId) {
        Optional<OrderEntity> order = orderEntityRepo.findById(orderId);
        if (!order.isPresent()) {
            throw new BusinessException(Code.ERROR, "订单id:" + orderId + "对应订单不存在");
        }
        OrderEntity orderEntity = order.get();
        if (!orderEntity.getOrderStatus().equals(OrderStatus.WAIT_PAY)) {
            throw new BusinessException(Code.ERROR, "订单id:" + orderId + "不能关闭");
        }
        //移交设备锁控制权或者释放锁
        transferOrReleaseDeviceLock(orderEntity.getDeviceId());
        //销毁订单
        orderEntity.setOrderStatus(OrderStatus.TRADE_CLOSE);
        orderEntity.setUpdateTime(String.valueOf(Instant.now().toEpochMilli()));
        orderEntityRepo.save(orderEntity);
    }

    private Map<String, Object> prePay(OrderEntity orderEntity) {
        UserAuthEntity userAuthEntity = userAuthService.getByUserIdAndType(AuthenticationHolder.getUserId(), IdentityType.WE_CHAT);

        UnifiedOrderDTO unifiedOrderDTO = new UnifiedOrderDTO();
        unifiedOrderDTO.setAppId(appId);
        //TODO
        unifiedOrderDTO.setMchId(mchId);
        unifiedOrderDTO.setNonceStr(WeChatUtil.genRandomStr());
//        unifiedOrderDTO.setBody(orderEntity.getOrderName());
        unifiedOrderDTO.setBody(orderEntity.getOrderName());
        unifiedOrderDTO.setOutTradeNo(orderEntity.getOrderNo());
        unifiedOrderDTO.setTotalFee(orderEntity.getSettlementAmount());

//        unifiedOrderDTO.setSpbillCreateIp(getIp(((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest()));
        unifiedOrderDTO.setSpbillCreateIp(selfIp);

        unifiedOrderDTO.setNotifyUrl(payCallBack);

        unifiedOrderDTO.setOpenid(userAuthEntity.getIdentifier());
        SortedMap sortedMap = wechatService.unifiedOrder(unifiedOrderDTO);

        sortedMap.put("type", 1);
        sortedMap.put("orderId", orderEntity.getId());
        sortedMap.put("orderNo", orderEntity.getOrderNo());
        return sortedMap;
    }

    private String getIp(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }

    private Map<String, Object> payWithoutCode(OrderEntity orderEntity, String contractId) {
        WeChatWithoutCodePayDTO weChatWithoutCodePayDTO = new WeChatWithoutCodePayDTO();
        weChatWithoutCodePayDTO.setAppId(appId);
        //TODO:商户id
        weChatWithoutCodePayDTO.setMchId(mchId);
        weChatWithoutCodePayDTO.setBody(orderEntity.getOrderName());
        weChatWithoutCodePayDTO.setNonceStr(WeChatUtil.genRandomStr());
        weChatWithoutCodePayDTO.setOutTradeNo(orderEntity.getOrderNo());
        weChatWithoutCodePayDTO.setTotalFee(String.valueOf(orderEntity.getSettlementAmount()));
        weChatWithoutCodePayDTO.setSpbillCreateIp(getIp(((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest()));
        weChatWithoutCodePayDTO.setNotifyUrl(payWithoutCodeCallBack);
        weChatWithoutCodePayDTO.setContractId(contractId);
        Map<String, Object> map = new HashMap<>();

        if (wechatService.delagePay(weChatWithoutCodePayDTO)) {
            map.put("type", 2);
            map.put("isSuccess", 1);
        }
        return map;
    }

    @Override
    @Transactional
    public Map<String, Object> payOrder(OrderPayVO orderPayVO) {
        WeChatContractEntity weChatContract = weChatContractRepo.findByCustomerId(AuthenticationHolder.getUserId());
        Optional<OrderEntity> order = orderEntityRepo.findById(orderPayVO.getOrderId());
        if (!order.isPresent()) {
            throw new BusinessException(Code.ERROR, "该订单不存在！");
        }
        OrderEntity orderEntity = order.get();
        handleOrderBeforePay(orderEntity, orderPayVO);
        // 如果是0元 直接已支付并启动
        if (NumberUtils.INTEGER_ZERO.equals(orderEntity.getSettlementAmount())) {
            if (!orderEntity.getOrderStatus().equals(OrderStatus.WAIT_PAY)) {
                throw new BusinessException(Code.ERROR, "订单已支付");
            }
            orderEntity.setTradeTime(System.currentTimeMillis() + "");
            //设置为已支付
            orderEntity.setOrderStatus(OrderStatus.PAID);
            // 保存订单
            orderEntityRepo.save(orderEntity);
            // 保存账单
            WeChatPaymentNotifyDTO notifyDTO = new WeChatPaymentNotifyDTO();
            notifyDTO.setTimeEnd(LocalDateTime.now().format(DateUtil.DATE_TIME_FORMATTER_NO_SEP));
            this.createBill(orderEntity, notifyDTO);
            // 启动设备
            delayTaskMessageSender.sendDelayMessage(new DelayMessage.Builder().orderId(orderEntity.getId()).deviceId(orderEntity.getDeviceId())
                    .type(DelayMsgType.EXPIRE_USER_PICK)
                    .userId(orderEntity.getCustomerId())
                    .consumerType(DelayConsumeAction.START_DEVICE)
                    .build(), 5 * 60 * 1000);
            SortedMap<String, Object> res = new TreeMap<>();
            res.put("appId", weChatProperties.getAppId());
            res.put("timeStamp", Instant.now().getEpochSecond());
            res.put("nonceStr", WeChatUtil.genRandomStr());
            res.put("package", "prepay_id=-1");
            res.put("signType", "MD5");
            String sign = WeChatUtil.sign(res, weChatProperties.getApiKey());
            res.put("paySign", sign);
            res.put("type", 1);
            res.put("orderId", orderEntity.getId());
            res.put("orderNo", orderEntity.getOrderNo());
            return res;
        }
        orderEntityRepo.save(orderEntity);
        //未签约免密支付 走预下单
        if (Objects.isNull(weChatContract)) {
            return prePay(orderEntity);
        }
        //签约免密支付的
        return payWithoutCode(orderEntity, weChatContract.getContractId());
    }

    /**
     * 快捷通 聚合扫码支付
     * H5页面订单支付
     *
     * @param orderPayVO
     */
    @Override
    public BasePayDTO payOrder4H5(OrderPayVO orderPayVO) {
        Optional<OrderEntity> order = orderEntityRepo.findById(orderPayVO.getOrderId());
        if (!order.isPresent()) {
            throw new BusinessException(Code.ERROR, "该订单不存在！");
        }
        OrderEntity orderEntity = order.get();
        if (OrderStatus.PAID.equals(orderEntity.getOrderStatus())) {
            throw new BusinessException(Code.ERROR, "该订单已支付，请勿重复支付");
        }
        //优惠券满减活动计算
        this.applyPromotion(orderPayVO, orderEntity);
        if (StringUtils.isNotBlank(orderPayVO.getUserCouponId())) {
            int updateCount = userCouponRepo.updateAfterUsed(orderPayVO.getUserCouponId(), UserCouponStatus.USED,
                    LocalDateTime.now(), orderEntity.getId(), orderEntity.getOrderNo());
            if (updateCount != 1) {
                throw new BusinessException(Code.ERROR, "代金券已被使用");
            }
        }
        orderEntityRepo.save(orderEntity);
        for (PayService payService : payServiceList) {
            if (payService.support(orderEntity)) {
                return payService.payOrder(orderEntity);
            }
        }
        throw new BusinessException(Code.ERROR, "错误支付方式");
    }

    public TradePreCreateRespDTO payOrder4H5(H5OrderPayVO h5OrderPayVO) {
        Optional<OrderEntity> order = orderEntityRepo.findById(h5OrderPayVO.getOrderId());
        if (!order.isPresent()) {
            throw new BusinessException(Code.ERROR, "该订单不存在！");
        }
        OrderEntity orderEntity = order.get();
        handleOrderBeforePay(orderEntity, h5OrderPayVO);
        // 如果是0元 直接已支付并启动
        if (NumberUtils.INTEGER_ZERO.equals(orderEntity.getSettlementAmount())) {
            if (!orderEntity.getOrderStatus().equals(OrderStatus.WAIT_PAY)) {
                throw new BusinessException(Code.ERROR, "订单已支付");
            }
            orderEntity.setTradeTime(System.currentTimeMillis() + "");
            //设置为已支付
            orderEntity.setOrderStatus(OrderStatus.PAID);
            // 保存订单
            orderEntityRepo.save(orderEntity);
            // 保存账单
            WeChatPaymentNotifyDTO notifyDTO = new WeChatPaymentNotifyDTO();
            notifyDTO.setTimeEnd(LocalDateTime.now().format(DateUtil.DATE_TIME_FORMATTER_NO_SEP));
            this.createBill(orderEntity, notifyDTO);
            // 启动设备
            delayTaskMessageSender.sendDelayMessage(new DelayMessage.Builder().orderId(orderEntity.getId()).deviceId(orderEntity.getDeviceId())
                    .type(DelayMsgType.EXPIRE_USER_PICK)
                    .userId(orderEntity.getCustomerId())
                    .consumerType(DelayConsumeAction.START_DEVICE)
                    .build(), 0);
            this.startDevice(orderEntity.getId());
            return null;
        }
        orderEntityRepo.save(orderEntity);

        TradePreCreateRespDTO resp = null;
        DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
        UserAuthEntity userAuth = null;
        switch (h5OrderPayVO.getPayType()) {
            case WECHAT_JSAPI:
                //1.判断当前用户有没有openid，没有则是未授权
                userAuth = userAuthService.getByUserIdAndType(orderEntity.getCustomerId(),
                        IdentityType.WE_CHAT_OFFICIAL_ACCOUNT);
                if (Objects.isNull(userAuth)) {
                    throw new BusinessException(Code.USER_NOT_AUTH_WE_CHAT);
                }

                TradePreCreateDTO.Memo memo = new TradePreCreateDTO.Memo();
                memo.setSpbillCreateIp(selfIp);
                memo.setAppid(weChatOfficialAppId);
                memo.setOpenid(userAuth.getIdentifier());
                memo.setTimeExpire(LocalDateTime.now().plusMinutes(10).format(df));
                TradePreCreateDTO tradePreCreateDTO = new TradePreCreateDTO().builder().transNo(orderEntity.getOrderNo())
                        .orderAmt(String.valueOf(orderEntity.getSettlementAmount()))
                        .goodsInfo(orderEntity.getOrderName())
                        .requestDate(df.format(LocalDateTime.now()))
                        .payType(h5OrderPayVO.getPayType())
                        .returnUrl(hsqProperty.getCallback())
                        .memo(JSON.toJSONString(memo))
                        .build();
                resp = hsqService.createUnifyTradeOrder(tradePreCreateDTO);
                BusinessOrderRelationEntity businessOrderRelationEntity = new BusinessOrderRelationEntity();
                businessOrderRelationEntity.setOrderId(orderEntity.getId());
                businessOrderRelationEntity.setOrderNo(orderEntity.getOrderNo());
                businessOrderRelationEntity.setWeChatOrderNo(resp.getChannelOrderNo());
                businessOrderRelationEntityRepo.save(businessOrderRelationEntity);
                break;
            case ALI_JSAPI:
                //1.判断当前用户有没有openid，没有则是未授权
                userAuth = userAuthService.getByUserIdAndType(orderEntity.getCustomerId(),
                        IdentityType.ALI_PAY);
                if (Objects.isNull(userAuth)) {
                    throw new BusinessException(Code.USER_NOT_AUTH_WE_CHAT);
                }
                TradePreCreateDTO.Memo memo1 = new TradePreCreateDTO.Memo();
                memo1.setSpbillCreateIp(selfIp);
                memo1.setAppid(alipayAppId);
                memo1.setOpenid(userAuth.getIdentifier());
                memo1.setTimeExpire(LocalDateTime.now().plusMinutes(10).format(df));
                TradePreCreateDTO dto = new TradePreCreateDTO().builder().transNo(orderEntity.getOrderNo())
                        .orderAmt(String.valueOf(orderEntity.getSettlementAmount()))
                        .goodsInfo(orderEntity.getOrderName())
                        .requestDate(df.format(LocalDateTime.now()))
                        .payType(h5OrderPayVO.getPayType())
                        .returnUrl(hsqProperty.getCallback())
                        .memo(JSON.toJSONString(memo1))
                        .build();
                resp = hsqService.createUnifyTradeOrder(dto);
                break;

        }
        return resp;
    }

    private void handleOrderBeforePay(OrderEntity orderEntity, OrderPayVO orderPayVO) {
        if (OrderStatus.PAID.equals(orderEntity.getOrderStatus())) {
            throw new BusinessException(Code.ERROR, "该订单已支付，请勿重复支付");
        }
        //优惠券满减活动计算
        this.applyPromotion(orderPayVO, orderEntity);
        if (StringUtils.isNotBlank(orderPayVO.getUserCouponId())) {
            int updateCount = userCouponRepo.updateAfterUsed(orderPayVO.getUserCouponId(), UserCouponStatus.USED,
                    LocalDateTime.now(), orderEntity.getId(), orderEntity.getOrderNo());
            if (updateCount != 1) {
                throw new BusinessException(Code.ERROR, "代金券已被使用");
            }
        }
    }

    @Override
    @Transactional
    public WeChatNotifyRespDTO paymentNotify(WeChatPaymentNotifyDTO paymentNotifyDTO) {
        String orderNo = paymentNotifyDTO.getOutTradeNo();
        OrderEntity orderEntity = orderEntityRepo.findByOrderNo(orderNo);
        weChatPaymentService.create(orderEntity.getCustomerId(), orderEntity.getId(), paymentNotifyDTO);
        WeChatTradeState tradeState = paymentNotifyDTO.getTradeState();
        switch (tradeState) {
            case SUCCESS:
                // 成功
                if (orderEntity.getOrderStatus().equals(OrderStatus.WAIT_PAY)) {
                    if (paymentNotifyDTO.getTotalFee().equals(orderEntity.getSettlementAmount())) {
                        orderEntity.setTradeTime(String.valueOf(System.currentTimeMillis()));
                        //设置为已支付
                        orderEntity.setOrderStatus(OrderStatus.PAID);
                        orderEntity.setPayPlatform(PayPlatform.WX_PAY);
                        //设置实际支付金额
                        orderEntity.setActualPayAmount(orderEntity.getSettlementAmount());
                        // 保存订单
                        OrderEntity save = orderEntityRepo.save(orderEntity);
                        // 保存账单
                        this.createBill(orderEntity, paymentNotifyDTO);
                        //5分钟后，如果设备没有在运行状态（筒自洁或者正常启动），则启动设备
                        DeviceEntity device = deviceEntityRepo.findById(save.getDeviceId()).orElseThrow(() -> new BusinessException(Code.NODATA));
                        delayTaskMessageSender.sendDelayMessage(new DelayMessage.Builder().orderId(save.getId()).deviceId(device.getId())
                                .type(DelayMsgType.EXPIRE_USER_PICK)
                                .userId(orderEntity.getCustomerId())
                                .consumerType(DelayConsumeAction.START_DEVICE)
                                .build(), 5 * 60 * 1000);
                    } else {
                        // 金额不对
                        log.error("微信免密支付通知，支付金额：{}和订单金额：{}不匹配", paymentNotifyDTO.getTotalFee(), orderEntity.getPrice());
                        return WeChatNotifyRespDTO.fail("支付金额错误");
                    }
                }
                break;
            case REFUND:
                // 转如退款，暂时不处理
                break;
            case NOTPAY:
                // 未支付，暂时不处理
                break;
            case CLOSED:
                // 支付失败
            case PAY_FAIL:
                // 订单关闭
                if (orderEntity.getOrderStatus().equals(OrderStatus.WAIT_PAY)) {
                    orderEntity.setOrderStatus(OrderStatus.ALREADY_CANCEL);
                }
                break;
            case ACCEPT:
                // 等待扣款，暂时不处理
                break;
        }
        WechatUserHolder.remove();
        return WeChatNotifyRespDTO.success();
    }

    /**
     * 快捷通支付回调
     *
     * @param paymentNotifyDTO
     * @return
     */
    @Override
    public boolean paymentNotify(KjtPaymentNotifyDTO paymentNotifyDTO) {
        kjtPaymentService.create(paymentNotifyDTO);
        String orderNo = paymentNotifyDTO.getOuterTradeNo();
        OrderEntity orderEntity = orderEntityRepo.findByOrderNo(orderNo);
        KjtTradeState tradeStatus = paymentNotifyDTO.getTradeStatus();
        switch (tradeStatus) {
            case TRADE_SUCCESS:
                break;
            case TRADE_FINISHED:
                // 成功
                String tradeAmountStr = paymentNotifyDTO.getTradeAmount();
                int tradeAmount = AmountUtil.strAmount2Int(tradeAmountStr);
                if (tradeAmount != orderEntity.getSettlementAmount()) {
                    log.error("快捷通支付通知，支付金额：{}和订单金额：{}不匹配", tradeAmount, orderEntity.getPrice());
                    return false;
                }
                LocalDateTime paymentEndTime = LocalDateTime.parse(paymentNotifyDTO.getGmtPayment(), KjtPaymentNotifyDTO.GMT_PAYMENT_FORMATTER);
                orderEntity.setPayPlatform(PayPlatform.KJT_PAT);
                this.handlePaySuccess(orderEntity, paymentEndTime);
                break;
            case TRADE_CLOSED:
                // 订单关闭
                if (orderEntity.getOrderStatus().equals(OrderStatus.WAIT_PAY)) {
                    orderEntity.setOrderStatus(OrderStatus.ALREADY_CANCEL);
                }
                break;
        }
        return true;
    }


    /**
     * 处理支付成功
     *
     * @param orderEntity
     * @param paymentEndTime
     */
    @Override
    public void handlePaySuccess(OrderEntity orderEntity, LocalDateTime paymentEndTime) {
        if (orderEntity.getOrderStatus().equals(OrderStatus.WAIT_PAY)) {
            orderEntity.setTradeTime(String.valueOf(System.currentTimeMillis()));
            //设置为已支付
            orderEntity.setOrderStatus(OrderStatus.PAID);
            orderEntity.setActualPayAmount(orderEntity.getSettlementAmount());
            // 保存订单
            OrderEntity save = orderEntityRepo.save(orderEntity);
            // 保存账单
            this.createBill(orderEntity, paymentEndTime);
            // 更新商户余额
//            merchantAccountService.updateAvailableBalance(orderEntity.getUserId(), orderEntity.getSettlementAmount());
            // 数据上链
            this.orderUpload2BlockChain(orderEntity);
            //5分钟后，如果设备没有在运行状态（筒自洁或者正常启动），则启动设备
            DeviceEntity device = deviceEntityRepo.findById(save.getDeviceId()).get();
            device.setStatus(DeviceStatus.STARTING);
            deviceEntityRepo.save(device);
            delayTaskMessageSender.sendDelayMessage(new DelayMessage.Builder().orderId(save.getId()).deviceId(orderEntity.getDeviceId())
                    .type(DelayMsgType.EXPIRE_USER_PICK)
                    .userId(orderEntity.getCustomerId())
                    .consumerType(DelayConsumeAction.START_DEVICE)
                    .build(), 5 * 1000);
        }
    }


    /**
     * 订单数据上链
     *
     * @param orderEntity
     */
    private void orderUpload2BlockChain(OrderEntity orderEntity) {
        taskExecutor.execute(() -> {
            OrderDTO orderDTO = new OrderDTO();
            orderDTO.setAmount(AmountUtil.strAmount2Int(orderEntity.getSettlementAmount().toString()));
            orderDTO.setCreateTime(System.currentTimeMillis());
            orderDTO.setDeviceId(orderEntity.getDeviceId());
            orderDTO.setOrderNo(orderEntity.getOrderNo());
            orderDTO.setUserId(orderEntity.getUserId());
            remoteOrderBlockChainService.commit(orderDTO);
        });
    }

    /**
     * 创建账单
     *
     * @param orderEntity
     * @param paymentNotifyDTO
     */
    public void createBill(OrderEntity orderEntity, Object paymentNotifyDTO) {
        LocalDateTime paymentEndTime = LocalDateTime.now();
        if (paymentNotifyDTO instanceof WeChatPaymentNotifyDTO) {
            paymentEndTime = LocalDateTime.parse(((WeChatPaymentNotifyDTO) paymentNotifyDTO).getTimeEnd(), DateUtil.DATE_TIME_FORMATTER_NO_SEP);
        } else if (paymentNotifyDTO instanceof CallbackResponseDTO) {
            paymentEndTime = LocalDateTime.parse(((CallbackResponseDTO) paymentNotifyDTO).getFinishedDate(), DateUtil.DATE_TIME_FORMATTER_NO_SEP);
        }

        BillCreateDTO createDTO = new BillCreateDTO();
        createDTO.setUserId(orderEntity.getCustomerId());
        createDTO.setOrderId(orderEntity.getId());
        DeviceModeDTO merchantMode = deviceModeService.getMerchantMode(orderEntity.getModeId());
        ProductType productType = merchantMode.getMetaMode().getProductType();
        ServiceType serviceType = null;
        switch (productType) {
            case WASHING_MACHINE:
                serviceType = ServiceType.CLOTHES_WASHING;
                break;
            case WASHING_SHOES_MACHINE:
                serviceType = ServiceType.SHOES_WASHING;
                break;
            case DRY_MACHINE:
                serviceType = ServiceType.CLOTHES_DRY;
                break;
        }
        createDTO.setServiceType(serviceType);
        createDTO.setBillType(BillType.PAY_SUCCESS);
        createDTO.setAmount(orderEntity.getSettlementAmount());
        createDTO.setCreateTime(paymentEndTime);
        billService.create(createDTO);
    }

    @Override
    @Transactional
    public BaseResult<?> startDevice(String orderId) {
        OrderEntity orderEntity = orderEntityRepo.findByIdAndCustomerId(orderId, AuthenticationHolder.getUserId());
        if (null == orderEntity) {
            return BaseResultFactory.produceResult(Code.NODATA, "订单不存在");
        }
        List<DeviceAlarmEntity> validAlarmList = deviceAlarmRepo.findAllByDeviceIdAndValid(orderEntity.getDeviceId(), true);
        if (!CollectionUtils.isEmpty(validAlarmList)) {
            log.error("根据订单：{}启动设备失败，设备存在故障告警: {}", orderId, validAlarmList);
            return BaseResultFactory.produceResult(Code.ERROR, "设备故障");
        }
        BaseResult<?> startRes = this.startDevice(orderEntity);
        if (startRes.getCode() == Code.SUCCESS.getCode()) {
            orderEntity.setOrderStatus(OrderStatus.IN_PROGRESS);
            if (orderEntity.getCommand() != null) {
                //启动液体泵
                delayTaskMessageSender.sendDelayMessage(new DelayMessage.Builder().orderId(orderEntity.getId())
                        .type(DelayMsgType.LIQUID_PUMP)
                        .userId(orderEntity.getCustomerId())
                        .consumerType(DelayConsumeAction.START_DEVICE)
                        .build(), 0);
            }
        }
        orderEntityRepo.save(orderEntity);
        return startRes;
    }

    @Override
    public BaseResult<?> startDevice(OrderEntity orderEntity) {
        if (!orderEntity.getOrderStatus().equals(OrderStatus.PAID)
                && !orderEntity.getOrderStatus().equals(OrderStatus.SELF_CLEANED)
                && !orderEntity.getOrderStatus().equals(OrderStatus.IN_PROGRESS)) {
            log.error("订单编号:{} 订单状态错误", orderEntity.getOrderNo());
            return BaseResultFactory.produceResult(Code.ERROR, "订单状态错误");
        }
        String modeId = orderEntity.getModeId();
        // 根据用户选择的模式获取对应的程序信息
        DeviceModeDTO merchantMode = deviceModeService.getMerchantMode(modeId);
        DeviceMetaModeDTO metaMode = merchantMode.getMetaMode();
        String deviceType = metaMode.getDeviceType();
        BaseResult<?> startRes = BaseResultFactory.produceEmptyResult(Code.ERROR);
        DeviceEntity device = deviceEntityRepo.findById(orderEntity.getDeviceId()).orElseThrow(() -> new BusinessException(Code.NODATA));
        // 重试三次
        for (int i = 0; i < 3; i++) {
            switch (deviceType) {
                case BaseConstant.GXY8_NAME:
                case BaseConstant.GXY13_5_NAME:
                    // 滚筒洗衣
                    RollerWasherOpVO deviceControlVO = deviceMetaModeService.metaModeToDeviceControlVO(metaMode);
                    deviceControlVO.setImei(device.getImei());
                    deviceControlVO.setDeviceType(deviceType);
                    deviceControlVO.setOrderId(orderEntity.getId());
                    startRes = deviceOpService.wash(deviceControlVO);
                    break;
                case BaseConstant.GGY8_NAME:
                case BaseConstant.GGY13_5_NAME:
                    // 滚筒干衣
                    DryerOperateVO dryerOperateVO = deviceMetaModeService.metaModeToDryerOperateVO(metaMode);
                    dryerOperateVO.setImei(device.getImei());
                    dryerOperateVO.setDeviceType(deviceType);
                    dryerOperateVO.setOrderId(orderEntity.getId());
                    dryerOperateVO.setWaitingTime(3);
                    dryerOperateVO.setDryTime(orderEntity.getCostTime());
                    dryerOperateVO.setStartEnabled(true);
                    startRes = deviceOpService.dry(dryerOperateVO);
                    break;
                case BaseConstant.BXY6_5_NAME:
                case BaseConstant.BXYD6_5_NAME:
                    // 波轮洗衣
                    PulseWasherOperateVO pulseWasherOperateVO = deviceMetaModeService.metaModeToPulseWasherOperateVO(metaMode);
                    pulseWasherOperateVO.setImei(device.getImei());
                    pulseWasherOperateVO.setDeviceType(deviceType);
                    pulseWasherOperateVO.setOrderId(orderEntity.getId());
                    pulseWasherOperateVO.setPulseNum(0);
                    startRes = deviceOpService.pulseWash(pulseWasherOperateVO);
                    break;
                case BaseConstant.BXX6_5_NAME:
                    // 洗鞋
                    ShoeWasherOperateVO shoeWasherOperateVO = deviceMetaModeService.metaModeToShoeWasherOperateVO(metaMode);
                    shoeWasherOperateVO.setImei(device.getImei());
                    shoeWasherOperateVO.setDeviceType(deviceType);
                    shoeWasherOperateVO.setOrderId(orderEntity.getId());
                    shoeWasherOperateVO.setPulseNum(0);
                    startRes = deviceOpService.shoeWash(shoeWasherOperateVO);
                    break;
            }
            if (startRes.getCode() == Code.SUCCESS.getCode()) {
                // 成功再删除
                stringRedisTemplate.delete(RedisKey.PAID_ORDER_DEVICE_START_LEFT_TIME_PREFIX + orderEntity.getId());
                break;
            }
        }
        return startRes;
    }

    @Override
    public DeviceStateInfoVO getDeviceState(String orderId) {
        OrderEntity orderEntity = orderEntityRepo.findByIdAndCustomerId(orderId, AuthenticationHolder.getUserId());
        if (null == orderEntity) {
            throw new BusinessException(Code.NODATA, "订单不存在");
        }
        DeviceEntity device = deviceEntityRepo.findById(orderEntity.getDeviceId()).orElseThrow(() -> new BusinessException(Code.NODATA));
        DeviceStateInfoVO res = new DeviceStateInfoVO();
        BeanUtil.copyProperties(device, res);
        res.setDeviceStatus(device.getStatus());
        String remainingTimeMM = devicePropertyService.getRealRemainTime(device.getId());
        res.setRemainTime(remainingTimeMM);
        return res;
    }

    @Override
    @Transactional
    public BaseResult<?> manualComplete(String orderId) {
        log.info("用户手动关闭订单：{}", orderId);
        OrderEntity orderEntity = orderEntityRepo.findByIdAndCustomerId(orderId, AuthenticationHolder.getUserId());
        if (null == orderEntity) {
            return BaseResultFactory.produceResult(Code.NODATA, "订单不存在");
        }
        if (!orderEntity.getOrderStatus().equals(OrderStatus.SELF_CLEANING)
                && !orderEntity.getOrderStatus().equals(OrderStatus.SELF_CLEANED)
                && !orderEntity.getOrderStatus().equals(OrderStatus.IN_PROGRESS)) {
            return BaseResultFactory.produceResult(Code.ERROR, "订单状态错误");
        }
        DeviceEntity deviceEntity = deviceEntityRepo.findById(orderEntity.getDeviceId()).orElseThrow(() -> new BusinessException(Code.NODATA));
        // 如果有告警不发送重置指令
        List<DeviceAlarmEntity> alarms = deviceAlarmRepo.findAllByDeviceIdAndValid(deviceEntity.getId(), true);
        if (!CollectionUtils.isEmpty(alarms)) {
            log.info("订单：{}所使用的设备故障，不发送复位指令", orderId);
        } else {
            BaseResult reset = deviceOpService.reset(deviceEntity.getImei());
            if (reset.getCode() != Code.SUCCESS.getCode()) {
                return reset;
            }
        }
        //更新订单状态
        orderEntity.setOrderStatus(OrderStatus.ALREADY_COMPLETE);
        orderEntity.setCompleteTime(String.valueOf(System.currentTimeMillis()));
        //服务时间计算
        long serviceTime = (System.currentTimeMillis() - Long.parseLong(orderEntity.getCreateTime())) / (1000 * 60);
        orderEntity.setServiceTime(Long.valueOf(serviceTime).intValue());
        orderEntityRepo.save(orderEntity);
        //订单结束消息提醒
        Map<String, String> params = new HashMap<>();
        buildMessageParam(params, orderEntity);
        try {
            wechatService.sendMessageToUser(orderEntity.getCustomerId(), WechatMsgType.ORDER_COMPLETE_REMIND, params);
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 如果筒自洁时间存在，进行删除
        if (StringUtils.isNotBlank(stringRedisTemplate.opsForValue().get(RedisKey.PAID_ORDER_DEVICE_START_LEFT_TIME_PREFIX + orderEntity.getId()))) {
            stringRedisTemplate.delete(RedisKey.PAID_ORDER_DEVICE_START_LEFT_TIME_PREFIX + orderEntity.getId());
        }
        transferOrReleaseDeviceLock(orderEntity.getDeviceId());

        return BaseResultFactory.produceEmptyResult(Code.SUCCESS);
    }

    @Override
    @Transactional
    public void transferOrReleaseDeviceLock(String deviceId) {
        //获取当前机器预约用户列表
        List<OrderEntity> subscribes = orderEntityRepo.findByDeviceIdAndOrderStatus(deviceId, OrderStatus.IN_ORDER);
        if (CollectionUtil.isNotEmpty(subscribes)) {
            // 如果当前预约列表不为空，移交设备锁控制权(最早预约的用户)
            OrderEntity next = subscribes.stream().sorted(Comparator.comparing(OrderEntity::getCreateTime)).findFirst().get();
            delayTaskMessageSender.sendDelayMessage(new DelayMessage.Builder().orderId(next.getId())
                    .type(DelayMsgType.EXPIRE_USER_SUBSCRIBE)
                    .userId(next.getCustomerId())
                    .consumerType(DelayConsumeAction.OPERATE_LOCK)
                    .build(), 10 * 60 * 1000);
            // 先设置剩余时间，再将锁给下一个人
            stringRedisTemplate.opsForValue().set(RedisKey.USER_SUBSCRIBE_LEFT_TIME_PREFIX + next.getId(), next.getId(), 10, TimeUnit.MINUTES);
            deviceService.lockTransfer(deviceId, next.getCustomerId(), 10);
            //微信消息推送
            Map<String, String> params = Maps.newHashMap();
            buildMessageParam(params, next);
            try {
                wechatService.sendMessageToUser(next.getCustomerId(), WechatMsgType.USER_SUBSCRIBE_REMIND, params);
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            //否则直接解除设备锁定
            deviceService.unlockDevice(deviceId);
        }
    }

    private void buildMessageParam(Map<String, String> params, OrderEntity order) {
        params.put("washPointName", order.getWashPointName());
        DeviceEntity deviceEntity = deviceEntityRepo.findById(order.getDeviceId()).get();
        StringBuilder deviceInfo = new StringBuilder();
        if (StringUtils.isNotBlank(deviceEntity.getConcretePositionId())) {
            try {
                WashPointConcretePositionEntity con = washPointConcretePositionEntityRepo.findById(deviceEntity.getConcretePositionId()).get();
                if (StringUtils.isNotBlank(con.getBuilding())) {
                    deviceInfo.append(con.getBuilding()).append("楼");
                }
                if (StringUtils.isNotBlank(con.getFloor())) {
                    deviceInfo.append(con.getFloor()).append("层");
                }
                if (StringUtils.isNotBlank(con.getRoom())) {
                    deviceInfo.append(con.getRoom()).append("室");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        if (deviceInfo.length() > 0) {
            deviceInfo.append("-").append(deviceEntity.getDeviceName());
        } else {
            deviceInfo.append(deviceEntity.getDeviceName());
        }
        params.put("deviceName", deviceInfo.toString());
    }

    /**
     * 获取订单支付后自动启动设备剩余时间
     * 筒自洁开始会暂停该时间
     *
     * @param orderEntity
     * @return
     */
    private Long getAutoStartLeftTime(OrderEntity orderEntity) {
        String autoStartLeftTime = stringRedisTemplate.opsForValue().get(RedisKey.PAID_ORDER_DEVICE_START_LEFT_TIME_PREFIX + orderEntity.getId());
        if (StringUtils.isBlank(autoStartLeftTime)) {
            return 5 * 60 - (System.currentTimeMillis() - Long.parseLong(orderEntity.getTradeTime())) / 1000;
        } else {
            return stringRedisTemplate.getExpire(RedisKey.PAID_ORDER_DEVICE_START_LEFT_TIME_PREFIX + orderEntity.getId(), TimeUnit.SECONDS);
        }
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void onDeviceStop(String deviceId) {
        // 查询筒自洁订单
        List<OrderEntity> selfCleaningOrderEntities = orderEntityRepo.findByDeviceIdAndOrderStatus(deviceId, OrderStatus.SELF_CLEANING);
        // 如果是筒自洁
        if (CollectionUtil.isNotEmpty(selfCleaningOrderEntities)) {
            OrderEntity orderEntity = selfCleaningOrderEntities.get(0);
            // 筒自洁结束
            orderEntity.setOrderStatus(OrderStatus.SELF_CLEANED);
            orderEntityRepo.saveAndFlush(orderEntity);
            String autoStartLeftTime = stringRedisTemplate.opsForValue().get(RedisKey.PAID_ORDER_DEVICE_START_LEFT_TIME_PREFIX + orderEntity.getId());
            stringRedisTemplate.opsForValue().set(RedisKey.PAID_ORDER_DEVICE_START_LEFT_TIME_PREFIX + orderEntity.getId(), autoStartLeftTime, Long.parseLong(autoStartLeftTime), TimeUnit.MILLISECONDS);
            // 倒计时自动启动设备
            delayTaskMessageSender.sendDelayMessage(new DelayMessage.Builder().orderId(orderEntity.getId()).deviceId(orderEntity.getDeviceId())
                    .type(DelayMsgType.EXPIRE_USER_PICK)
                    .userId(orderEntity.getCustomerId())
                    .consumerType(DelayConsumeAction.START_DEVICE)
                    .build(), Long.valueOf(autoStartLeftTime).intValue());
            return;
        }
        List<OrderEntity> orderEntities = orderEntityRepo.findByDeviceIdAndOrderStatus(deviceId, OrderStatus.IN_PROGRESS);
        if (CollectionUtils.isEmpty(orderEntities)) {
            return;
        }
        OrderEntity orderEntity = orderEntities.get(0);
        orderEntity.setOrderStatus(OrderStatus.ALREADY_COMPLETE);
        orderEntity.setCompleteTime(String.valueOf(System.currentTimeMillis()));
        //服务时间计算
        long serviceTime = (System.currentTimeMillis() - Long.parseLong(orderEntity.getCreateTime())) / (1000 * 60);
        orderEntity.setServiceTime(Long.valueOf(serviceTime).intValue());
        orderEntityRepo.saveAndFlush(orderEntity);
        //订单结束消息提醒
        Map<String, String> params = new HashMap<>();
        buildMessageParam(params, orderEntity);
        try {
            wechatService.sendMessageToUser(orderEntity.getCustomerId(), WechatMsgType.ORDER_COMPLETE_REMIND, params);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //解除设备锁定
        UserAuthentication userAuthentication = new UserAuthentication(orderEntity.getCustomerId(), orderEntity.getCustomerName(), Sets.newHashSet(RoleType.CUSTOMER.name()));
        AuthenticationHolder.setUserAuth(userAuthentication);
        try {
            transferOrReleaseDeviceLock(orderEntity.getDeviceId());
        } finally {
            AuthenticationHolder.clear();
        }
    }

    private Page<OrderEntity> searchUserOrder(Pageable pageable, Set<OrderStatus> orderStatus, String deviceId) {
        Optional<UserEntity> user = userEntityRepo.findById(AuthenticationHolder.getUserId());
        UserEntity userEntity = user.get();
        return orderEntityRepo.findAll((root, query, criteriaBuilder) -> {
            List<Predicate> list = new ArrayList<>();
            list.add(criteriaBuilder.equal(root.get("customerPhone"), userEntity.getPhone()));
            list.add(root.get("orderStatus").in(orderStatus));
            if (StringUtils.isNotBlank(deviceId)) {
                list.add(criteriaBuilder.equal(root.get("deviceId"), deviceId));
            }
            Predicate[] predicates = new Predicate[list.size()];
            return criteriaBuilder.and(list.toArray(predicates));
        }, pageable);
    }

    private Long computeTotalCount(String startTime, String endTime, String keyword,
                                   String deviceType, OrderStatus orderStatus, String projectId) {
        StringBuilder countSql = new StringBuilder("select count(*) " +
                "from device_order o LEFT JOIN device d on o.device_id = d.id  LEFT JOIN project p on d.project_id = p.id left join loan l on p.loan_id = l.id " +
                " LEFT JOIN user u on d.user_id = u.id where 1=1 ");
        if (AuthenticationHolder.isFinancialInstitutions()) {
            countSql.append(" and l.lender_id = :lenderId");
        }
        //既不是管理员也不是金融机构，判断设备归属
        if (!AuthenticationHolder.isFinancialInstitutions() && !AuthenticationHolder.isAdmin()) {
            countSql.append(" and d.user_id = :userId");
        }
        if (!StringUtils.isBlank(projectId)) {
            countSql.append(" and p.id = :projectId");
        }
        if (!StringUtils.isBlank(startTime)) {
            countSql.append(" and o.create_time >= :startTime");
        }
        if (!StringUtils.isBlank(endTime)) {
            countSql.append(" and o.create_time <= :endTime");
        }
        if (!StringUtils.isBlank(keyword)) {
            countSql.append(" and (o.order_no like :keyword or d.address like :keyword or u.enterprise_name like :keyword" +
                    " or o.customer_phone like :keyword)");
        }
        if (!StringUtils.isBlank(deviceType)) {
            countSql.append(" and d.product_type = :deviceType");
        }
        if (null != orderStatus) {
            countSql.append(" and o.order_status = :orderStatus");
        }
        Query query = entityManager.createNativeQuery(countSql.toString());
        if (AuthenticationHolder.isFinancialInstitutions()) {
            query.setParameter("lenderId", AuthenticationHolder.getUserId());
        }
        if (!StringUtils.isBlank(projectId)) {
            query.setParameter("projectId", projectId);
        }
        if (!AuthenticationHolder.isFinancialInstitutions() && !AuthenticationHolder.isAdmin()) {
            query.setParameter("userId", AuthenticationHolder.getUserId());
        }
        if (!StringUtils.isBlank(startTime)) {
            query.setParameter("startTime", startTime);
        }
        if (!StringUtils.isBlank(endTime)) {
            query.setParameter("endTime", endTime);
        }

        if (!StringUtils.isBlank(keyword)) {
            query.setParameter("keyword", "%" + keyword + "%");
        }
        if (!StringUtils.isBlank(deviceType)) {
            query.setParameter("deviceType", deviceType);
        }
        if (null != orderStatus) {
            query.setParameter("orderStatus", orderStatus);
        }
        BigInteger count = QueryUtil.getSingleResult(query, BigInteger.ZERO);
        return count.longValue();
    }

    @Override
    public OMRevenueStatisticsVO omRevenueStatisticsVO(String preferWashPointId) {
        StringBuilder todayAmountSql = new StringBuilder("select sum(do.price) from device_order do left join device d on do.device_id=d.id" +
                " where do.user_id=:userId and do.create_time>=:tradeTimeStart and do.order_status='ALREADY_COMPLETE'");
        StringBuilder todayCountSql = new StringBuilder("select count(do.id) from device_order do left join device d on do.device_id=d.id" +
                " where do.user_id=:userId and do.create_time>=:tradeTimeStart and do.order_status='ALREADY_COMPLETE'");
        StringBuilder totalAmountSql = new StringBuilder("select sum(do.actual_pay_amount) from device_order do left join device d on do.device_id=d.id" +
                " where do.user_id=:userId and do.order_status='ALREADY_COMPLETE'");
        if (StringUtils.isNotEmpty(preferWashPointId)) {
            todayAmountSql.append(" and do.wash_point_id=:washPointId");
            todayCountSql.append(" and do.wash_point_id=:washPointId");
            totalAmountSql.append(" and do.wash_point_id=:washPointId");
        }
        Query todayAmountQuery = entityManager.createNativeQuery(todayAmountSql.toString());
        Query todayCountQuery = entityManager.createNativeQuery(todayCountSql.toString());
        Query totalAmountQuery = entityManager.createNativeQuery(totalAmountSql.toString());
        LocalDateTime todayStart = LocalDateTime.of(LocalDate.now(), LocalTime.MIN);
        String todayStartStr = DateUtil.toEpochMilli(todayStart) + "";
        String currentId = AuthenticationHolder.getUserId();
        UserEntity userMerchant = userRelService.getUserMerchant(currentId);
        todayAmountQuery.setParameter("userId", userMerchant.getId());
        todayAmountQuery.setParameter("tradeTimeStart", todayStartStr);
        todayCountQuery.setParameter("userId", userMerchant.getId());
        todayCountQuery.setParameter("tradeTimeStart", todayStartStr);
        totalAmountQuery.setParameter("userId", userMerchant.getId());
        if (StringUtils.isNotEmpty(preferWashPointId)) {
            todayAmountQuery.setParameter("washPointId", preferWashPointId);
            todayCountQuery.setParameter("washPointId", preferWashPointId);
            totalAmountQuery.setParameter("washPointId", preferWashPointId);
        }
        BigDecimal todayAmount = QueryUtil.getSingleResult(todayAmountQuery, BigDecimal.ZERO);
        BigInteger todayCount = QueryUtil.getSingleResult(todayCountQuery, BigInteger.ZERO);
        BigDecimal totalAmount = QueryUtil.getSingleResult(totalAmountQuery, BigDecimal.ZERO);
        return new OMRevenueStatisticsVO(AmountUtil.amountRoundStr(todayAmount, 2), todayCount.longValue(),
                AmountUtil.amountRoundStr(totalAmount, 2));
    }

    @Override
    public PageResult queryListForOperPlat(Pageable pageable, String keyword, Set<OrderStatus> status, Set<ProductType> type, String startTime, String endTime) {
        Page<OrderEntity> all = orderEntityRepo.findAll(assembleCondition(keyword, status, type, startTime, endTime), pageable);
        List<DeviceOrderListForOperVO> deviceOrderListForOperVOS = switchToOperListVO(all.getContent());
        if (type.contains(ProductType.WASHING_MACHINE)) {
            return new PageResult(all.getTotalElements(), calculateTheConsumerPriceOfWashingMachines(deviceOrderListForOperVOS));
        } else if (type.contains(ProductType.LIQUID_PUMP)) {
            return new PageResult(all.getTotalElements(), liquidPumpOrder(deviceOrderListForOperVOS));
        }
        return new PageResult(all.getTotalElements(), deviceOrderListForOperVOS);
    }

    @Autowired
    private LiquidPumpOrderEntityRepo liquidPumpOrderEntityRepo;

    /**
     * 计算洗衣机消费价格
     */
    public List<DeviceOrderListForOperVO> calculateTheConsumerPriceOfWashingMachines(List<DeviceOrderListForOperVO> deviceOrderListForOperVOS) {
        deviceOrderListForOperVOS.stream().forEach(deviceOrderListForOperVO -> {
            if (deviceOrderListForOperVO.getCommand() != null) {
                LiquidPumpOrderEntity byOrderId = liquidPumpOrderEntityRepo.findByOrderId(deviceOrderListForOperVO.getId());
                if (byOrderId == null) {
                    return;
                }
                Integer price = 0;
                if (byOrderId.getFirstPumpPrice() != null) {
                    price = price + byOrderId.getFirstPumpPrice();
                }
                if (byOrderId.getSecondPumpPrice() != null) {
                    price = price + byOrderId.getSecondPumpPrice();
                }
                Double aDoublePrice = Double.parseDouble(deviceOrderListForOperVO.getPrice()) - Double.parseDouble(AmountUtil.amountRoundStr(price, 2));
                deviceOrderListForOperVO.setPrice(aDoublePrice.toString());
                deviceOrderListForOperVO.setSettlementPrice(aDoublePrice.toString());
            }
        });
        return deviceOrderListForOperVOS;
    }

    /**
     * 液体泵订单查询
     *
     * @param deviceOrderListForOperVOS
     * @return
     */
    public List<DeviceOrderListForOperVO> liquidPumpOrder(List<DeviceOrderListForOperVO> deviceOrderListForOperVOS) {
        deviceOrderListForOperVOS.stream().forEach(deviceOrderListForOperVO -> {
            LiquidPumpOrderEntity byOrderId = liquidPumpOrderEntityRepo.findByOrderId(deviceOrderListForOperVO.getId());
            if (byOrderId == null) {
                return;
            }
            Integer price = 0;
            if (byOrderId.getFirstPumpPrice() != null) {
                price = price + byOrderId.getFirstPumpPrice();
            }
            if (byOrderId.getSecondPumpPrice() != null) {
                price = price + byOrderId.getSecondPumpPrice();
            }
            deviceOrderListForOperVO.setDiscountPrice("0.00");
            deviceOrderListForOperVO.setPrice(AmountUtil.amountRoundStr(price, 2));
            deviceOrderListForOperVO.setSettlementPrice(AmountUtil.amountRoundStr(price, 2));
            deviceOrderListForOperVO.setProductType(ProductType.LIQUID_PUMP);
        });
        return deviceOrderListForOperVOS;
    }

    /**
     * 计算洗衣机消费价格
     *
     * @param exportOrderForOperVOS
     * @return
     */
    public List<ExportOrderForOperVO> calculateTheConsumerPriceOfWashingMachinesExport(List<ExportOrderForOperVO> exportOrderForOperVOS) {
        exportOrderForOperVOS.stream().forEach(exportOrderForOperVO -> {
            LiquidPumpOrderEntity byOrderId = liquidPumpOrderEntityRepo.findByOrderId(exportOrderForOperVO.getId());
            if (byOrderId == null) {
                return;
            }
            Integer price = 0;
            if (byOrderId.getFirstPumpPrice() != null) {
                price = price + byOrderId.getFirstPumpPrice();
            }
            if (byOrderId.getSecondPumpPrice() != null) {
                price = price + byOrderId.getSecondPumpPrice();
            }
            Double aDoublePrice = Double.parseDouble(exportOrderForOperVO.getPrice()) - Double.parseDouble(AmountUtil.amountRoundStr(price, 2));
            exportOrderForOperVO.setPrice(aDoublePrice.toString());
            exportOrderForOperVO.setSettlementPrice(aDoublePrice.toString());
        });
        return exportOrderForOperVOS;
    }


    /**
     * 液体泵订单导出
     *
     * @param exportOrderForOperVOS
     * @return
     */
    public List<ExportOrderForOperVO> liquidPumpOrderExport(List<ExportOrderForOperVO> exportOrderForOperVOS) {
        exportOrderForOperVOS.stream().forEach(exportOrderForOperVO -> {
            LiquidPumpOrderEntity byOrderId = liquidPumpOrderEntityRepo.findByOrderId(exportOrderForOperVO.getId());
            if (byOrderId == null) {
                return;
            }
            Integer price = 0;
            if (byOrderId.getFirstPumpPrice() != null) {
                price = price + byOrderId.getFirstPumpPrice();
            }
            if (byOrderId.getSecondPumpPrice() != null) {
                price = price + byOrderId.getSecondPumpPrice();
            }
            exportOrderForOperVO.setDiscountPrice("0.00");
            exportOrderForOperVO.setPrice(AmountUtil.amountRoundStr(price, 2));
            exportOrderForOperVO.setSettlementPrice(AmountUtil.amountRoundStr(price, 2));
            exportOrderForOperVO.setProductType(ProductType.LIQUID_PUMP.name());
        });
        return exportOrderForOperVOS;
    }

    @Override
    public List<ExportOrderForOperVO> exportForOperPlat(Set<String> ids, String keyword, Set<OrderStatus> status,
                                                        Set<ProductType> productType, String startTime, String endTime, String all) {
        if (CollectionUtil.isNotEmpty(ids)) {
            return switchToExportVO(Lists.newArrayList(orderEntityRepo.findAllById(ids)));
        } else {
            List<ExportOrderForOperVO> exportOrderForOperVOS = switchToExportVO(orderEntityRepo.findAll(assembleCondition(keyword, status, productType, startTime, endTime)));
            if (productType.contains(ProductType.WASHING_MACHINE)) {
                return calculateTheConsumerPriceOfWashingMachinesExport(exportOrderForOperVOS);
            } else if (productType.contains(ProductType.LIQUID_PUMP)) {
                return liquidPumpOrderExport(exportOrderForOperVOS);
            }
            return exportOrderForOperVOS;
        }
    }

    @Override
    public SubscribeDeviceReVO subscribeIdleDevice(OrderGenerateVO orderGenerateVO) {
        SubscribeDeviceReVO re = new SubscribeDeviceReVO();
        DeviceEntity device = deviceEntityRepo.findByMachineNo(orderGenerateVO.getMachineNo());
        if (Objects.isNull(device)) {
            throw new BusinessException(Code.NODATA);
        }
        //重复预约
        if (CollectionUtil.isNotEmpty(orderEntityRepo.findByCustomerIdAndDeviceIdAndOrderStatus(AuthenticationHolder.getUserId(), device.getId(), OrderStatus.IN_ORDER))) {
            throw new BusinessException(Code.ERROR, "您已预约过这台设备，请勿重复预约");
        }
//        Long subscribeCount = orderEntityRepo.countAllByCustomerIdAndOrderStatusAndIsSubscribe(AuthenticationHolder.getUserId(), OrderStatus.IN_ORDER, true);
        Long subscribeCount = getUserSubscribeDeviceCount(Objects.isNull(device.getProductType()) ? "" : device.getProductType().name());

        if (subscribeCount == 2) {
            re.setIsSuccess(false);
            re.setErrorType("0");
            re.setMessage("预约失败！检测到您已预约了两台设备,请先使用完预约的设备后，再预约新设备！");
            return re;
        }
        //如果设备已被他人锁定
        if (!deviceService.lockDevice(device.getId(), 10)) {
            computeWaitQueueInfo(device.getId(), re, false);
            re.setIsSuccess(false);
            re.setErrorType("1");
            re.setMessage("预约失败，被别人抢先了！");
            return re;
        } else {
            computeWaitQueueInfo(device.getId(), re, false);
            OrderEntity orderEntity = assembleOrder(device, OrderStatus.IN_ORDER, orderGenerateVO);
            orderEntity.setIsSubscribe(true);
            OrderEntity save = orderEntityRepo.save(orderEntity);
            // 预约成功且设备空闲
            delayTaskMessageSender.sendDelayMessage(new DelayMessage.Builder().orderId(save.getId())
                    .type(DelayMsgType.EXPIRE_USER_SUBSCRIBE)
                    .userId(AuthenticationHolder.getUserId())
                    .consumerType(DelayConsumeAction.START_DEVICE)
                    .build(), 10 * 60 * 1000);
            stringRedisTemplate.opsForValue().set(RedisKey.USER_SUBSCRIBE_LEFT_TIME_PREFIX + save.getId(), save.getId(), 10, TimeUnit.MINUTES);
            re.setOrderId(save.getId());
            re.setIsSuccess(true);
            re.setMessage("预约成功！去找到你预订编号的设备吧！预约保留时间：10分钟");
        }
        return re;
    }

    private Long getUserSubscribeDeviceCount(String productType) {
        StringBuilder sb = new StringBuilder("select count(*) from device_order o left join device d on o.device_id=d.id where o.customer_id=:userId" +
                " and o.order_status in :statusSet and d.product_type=:productType");

        Query nativeQuery = entityManager.createNativeQuery(sb.toString());
        nativeQuery.setParameter("statusSet", OrderStatus.PROCESSING_STATUS_SET.stream().map(x -> x.name()).collect(Collectors.toList()));
        nativeQuery.setParameter("userId", AuthenticationHolder.getUserId());
        nativeQuery.setParameter("productType", productType);
        BigInteger count = QueryUtil.getSingleResult(nativeQuery, BigInteger.ZERO);
        return count.longValue();
    }

    private void computeWaitQueueInfo(String deviceId, WaitQueueInfo re, Boolean isAfterSubscribe) {
        //计算排队人数和时间
        List<OrderEntity> orders = orderEntityRepo.findAllByDeviceIdAndOrderStatusIn(deviceId, OrderStatus.PROCESSING_STATUS_SET);
        if (isAfterSubscribe) {
            OrderEntity self = orders.stream().filter(x -> AuthenticationHolder.getUserId().equals(x.getCustomerId()) && OrderStatus.IN_ORDER.equals(x.getOrderStatus())).findFirst().get();
            List<OrderEntity> collect = orders.stream().filter(x -> Long.parseLong(x.getCreateTime()) < Long.parseLong(self.getCreateTime())).collect(Collectors.toList());
            int waitTime = collect.stream().filter(x -> OrderStatus.IN_ORDER.equals(x.getOrderStatus())).mapToInt(OrderEntity::getCostTime).sum();
            String remainingTimeMM = devicePropertyService.getRealRemainTime(self.getDeviceId());
            Integer waitPersonCount = collect.size() > 0 ? collect.size() - collect.stream().filter(x -> !OrderStatus.IN_ORDER.equals(x.getOrderStatus())).findAny().map(x -> 1).orElse(0) : 0;
            re.setWaitPersonCount(waitPersonCount);
            int remainTime = StringUtils.isBlank(remainingTimeMM) ? 0 : Integer.parseInt(remainingTimeMM);
            re.setWaitTime(waitTime + remainTime);
        } else {
            int remainTime = 0;
            if (CollectionUtil.isNotEmpty(orders)) {
                String remainingTimeMM = devicePropertyService.getRealRemainTime(orders.get(0).getDeviceId());
                remainTime = StringUtils.isBlank(remainingTimeMM) ? 0 : Integer.parseInt(remainingTimeMM);
            }
            int waitTime = orders.stream().filter(x -> OrderStatus.IN_ORDER.equals(x.getOrderStatus())).mapToInt(OrderEntity::getCostTime).sum();
            //计算排队人数，如果当前使用的人已经扫设备，则不视为排队人数
            Integer waitPersonCount = orders.size() > 0 ? orders.size() - orders.stream().filter(x -> !OrderStatus.IN_ORDER.equals(x.getOrderStatus())).findAny().map(x -> 1).orElse(0) : 0;
            re.setWaitPersonCount(waitPersonCount);
            re.setWaitTime(waitTime + remainTime);
        }

    }

    @Override
    @Transactional
    public SubscribeDeviceReVO userJoinQueue(OrderGenerateVO orderGenerateVO) {

        SubscribeDeviceReVO re = new SubscribeDeviceReVO();
        DeviceEntity device = deviceEntityRepo.findByMachineNo(orderGenerateVO.getMachineNo());
        Long subscribeCount = getUserSubscribeDeviceCount(Objects.isNull(device.getProductType()) ? "" : device.getProductType().name());
        if (subscribeCount == 2) {
            re.setIsSuccess(false);
            re.setErrorType("0");
            re.setMessage("预约失败！检测到您已预约了两台设备,请先使用完预约的设备后，再预约新设备！");
            return re;
        }
        //重复预约

        if (CollectionUtil.isNotEmpty(orderEntityRepo.findByCustomerIdAndDeviceIdAndOrderStatus(AuthenticationHolder.getUserId(), device.getId(), OrderStatus.IN_ORDER))) {
            throw new BusinessException(Code.ERROR, "您已预约过这台设备，请勿重复预约");
        }
        computeWaitQueueInfo(device.getId(), re, false);
        if (re.getWaitTime() == 0 && re.getWaitPersonCount() == 0) {
            if (stringRedisTemplate.opsForValue().get(RedisKey.DeviceOperate.DEVICE_LOCK_KEY_PREFIX + device.getId()) == null) {
                throw new BusinessException(Code.NOT_IN_USE_JOIN_QUEUE);
            }
        }
        OrderEntity orderEntity = assembleOrder(device, OrderStatus.IN_ORDER, orderGenerateVO);
        OrderEntity save = orderEntityRepo.save(orderEntity);
        re.setIsSuccess(true);
        re.setOrderId(save.getId());
        return re;
    }

    @Override
    @Transactional
    public void userCancelSubscribe(String orderId) {
        OrderEntity orderEntity = orderEntityRepo.findById(orderId).orElseThrow(() -> new BusinessException(Code.NODATA));
        orderEntity.setOrderStatus(OrderStatus.TRADE_CLOSE);
        // 如果取消预约的人持有设备的锁，则将锁转移给下一个排队的人
        if (AuthenticationHolder.getUserId().equals(stringRedisTemplate.opsForValue().get(RedisKey.DeviceOperate.DEVICE_LOCK_KEY_PREFIX + orderEntity.getDeviceId()))) {
            transferOrReleaseDeviceLock(orderEntity.getDeviceId());
        }
        orderEntityRepo.save(orderEntity);
    }

    @Override
    public List<UserPickDeviceVO> getUserPickDeviceList(String washPointId, ProductType productType) {
        List<DeviceEntity> allDevice = deviceEntityRepo.findAll(deviceService.customerAvailableDeviceSpec(washPointId));
        return allDevice.stream().filter(x -> {
            if (Objects.isNull(productType)) {
                return true;
            }
            return productType.equals(x.getProductType());
        }).filter(x -> deviceService.inServiceTime(x.getServiceTimeId())).map(x -> {
            UserPickDeviceVO re = new UserPickDeviceVO();
            BeanUtil.copyProperties(x, re);
            computeWaitQueueInfo(x.getId(), re, false);
            if (!DeviceStatus.NOT_RUNNING.equals(x.getStatus()) || Objects.nonNull(stringRedisTemplate.opsForValue().get(RedisKey.DeviceOperate.DEVICE_LOCK_KEY_PREFIX + x.getId()))) {
                re.setStatus(DeviceStatusForUser.BUSY.name());
            } else {
                re.setStatus(DeviceStatusForUser.IDLE.name());
            }
            if (StringUtils.isNotBlank(x.getConcretePositionId())) {
                WashPointConcretePositionEntity concretePositionEntity = washPointConcretePositionEntityRepo.findById(x.getConcretePositionId()).get();
                re.setBuilding(concretePositionEntity.getBuilding());
                re.setFloor(concretePositionEntity.getFloor());
                re.setRoom(concretePositionEntity.getRoom());
            }
            return re;
        }).collect(Collectors.toList());
    }

    @Override
    public Long getOrderPayLeftTime(String orderId) {
        return this.getExpire(RedisKey.PAY_ORDER_LEFT_TIME_PREFIX + orderId);
    }

    @Override
    public Long getUserChoiceLeftTime(String orderId) {
        OrderEntity orderEntity = orderEntityRepo.findById(orderId).orElseThrow(() -> new BusinessException(Code.NODATA));
        return this.getAutoStartLeftTime(orderEntity);
    }

    @Override
    public Long getUserSubscribeLeftTime(String orderId) {
        return this.getExpire(RedisKey.USER_SUBSCRIBE_LEFT_TIME_PREFIX + orderId);
    }

    @Override
    public List<OrderEntity> queryForScanSkip(String deviceId, OrderStatus status) {
        return searchUserOrder(new PageRequest(1, 10), Sets.newHashSet(status), deviceId).getContent();
    }

    @Override
    public UserWaitQueue getUserWaitQueueInfo(String deviceId, Boolean isAfterSubscribe) {
        UserWaitQueue re = new UserWaitQueue();
        DeviceEntity byMachineNo = deviceEntityRepo.findByMachineNo(deviceId);
        computeWaitQueueInfo(byMachineNo.getId(), re, isAfterSubscribe);
        if (Objects.isNull(byMachineNo)) {
            throw new BusinessException(Code.NODATA);
        }
        HashSet<OrderStatus> orderStatuses = Sets.newHashSet(OrderStatus.PROCESSING_STATUS_SET);
        orderStatuses.remove(OrderStatus.IN_ORDER);
        re.setIsInTurn(!deviceService.deviceLocked(byMachineNo.getId()) && !DeviceStatus.RUNNING.equals(byMachineNo.getStatus()) && CollectionUtil.isEmpty(orderEntityRepo.findAllByDeviceIdAndOrderStatusIn(byMachineNo.getId(), orderStatuses)));
        return re;
    }

    @Override
    public OrderEntity refund(String orderNo) {
        OrderEntity entity = orderEntityRepo.findByOrderNo(orderNo);
        if (entity.getCustomerId() != AuthenticationHolder.getUserId()) {
            throw new BusinessException(Code.ERROR, "非法退款");
        }
        OrderRefundEntity orderRefundEntity = orderRefundEntityRepo.findByOrderNoAndRefundReview(orderNo);
        if (!(cn.hutool.core.date.DateUtil.offsetDay(new Date(Long.valueOf(entity.getCreateTime())), 3).getTime() > System.currentTimeMillis())) {
            throw new BusinessException(Code.ERROR, "该订单支付超出三天不支持退款");
        } else if (orderRefundEntity != null) {
            throw new BusinessException(Code.ERROR, "该订单不支持重复发起退款");
        } else if (entity.getOrderStatus() == OrderStatus.REFUND) {
            throw new BusinessException(Code.ERROR, "该订单已退款");
        } else if (entity.getOrderStatus() == OrderStatus.ALREADY_COMPLETE) {
            return refund(entity);
        }
        throw new BusinessException(Code.ERROR, entity.getOrderStatus().getMessage());
    }

    @Override
    public double countOrderList(String keyword, Set<OrderStatus> status, Set<ProductType> productType, String startTime, String endTime) {
        PageResult<DeviceOrderListForOperVO> list = this.queryListForOperPlat(PageRequest.of(0, 99999), keyword, status, productType, startTime, endTime);
        List<DeviceOrderListForOperVO> elements = list.getElements();
        if(!CollectionUtils.isEmpty(elements)){
            // 分
            return elements.stream().mapToDouble(e -> Double.parseDouble(e.getSettlementPrice())).sum();
        }
        return 0;
    }

    @Autowired
    private OrderRefundEntityRepo orderRefundEntityRepo;

    public OrderEntity refund(OrderEntity entity) {
        OrderRefundEntity orderRefundEntity = new OrderRefundEntity();
        BeanUtils.copyProperties(entity, orderRefundEntity);
        orderRefundEntity.setPrice(entity.getSettlementAmount());
        orderRefundEntity.setMerchantId(entity.getUserId());
        orderRefundEntity.setOrderRefundNo(orderNoGenUtil.next());
        orderRefundEntity.setRefundReview(RefundReview.PENDING_REVIEW);
        orderRefundEntityRepo.save(orderRefundEntity);
        return entity;
    }

    /**
     * 获取Key剩余过期时间
     *
     * @param key
     * @return
     */
    private Long getExpire(String key) {
        if (Objects.isNull(stringRedisTemplate.opsForValue().get(key))) {
            return 0L;
        }
        return stringRedisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    private List<ExportOrderForOperVO> switchToExportVO(List<OrderEntity> list) {
        return list.stream().map(x -> {
            ExportOrderForOperVO copy = BeanCopyUtil.copy(x, ExportOrderForOperVO.class);
            copy.setTradeTime(DateUtil.longToString(Long.parseLong(x.getCreateTime()), "yyyy-MM-dd HH:mm:ss"));
            copy.setPrice(AmountUtil.amountRoundStr(x.getPrice(), 2));
            copy.setDiscountPrice(AmountUtil.amountRoundStr(x.getDiscountPrice(), 2));
            copy.setSettlementPrice(AmountUtil.amountRoundStr(x.getSettlementAmount(), 2));
            copy.setStatus(x.getOrderStatus().getMessage());
            if (null != x.getProductType()) {
                copy.setProductType(x.getProductType().getDesc());
            }
            return copy;
        }).collect(Collectors.toList());
    }

    private List<DeviceOrderListForOperVO> switchToOperListVO(List<OrderEntity> list) {
        return list.stream().map(x -> {
            DeviceOrderListForOperVO copy = BeanCopyUtil.copy(x, DeviceOrderListForOperVO.class);
            copy.setTradeTime(x.getCreateTime());
            copy.setPrice(AmountUtil.amountRoundStr(x.getPrice(), 2));
            copy.setDiscountPrice(AmountUtil.amountRoundStr(x.getDiscountPrice(), 2));
            copy.setSettlementPrice(AmountUtil.amountRoundStr(x.getSettlementAmount(), 2));
            copy.setStatus(x.getOrderStatus());
            return copy;
        }).collect(Collectors.toList());
    }


    private Specification<OrderEntity> assembleCondition(String keyword, Set<OrderStatus> status, Set<ProductType> type, String startTime, String endTime) {
        return (root, query, criteriaBuilder) -> {
            List<Predicate> predicates = Lists.newArrayList();

            if (!AuthenticationHolder.isAdmin()) {
                String currentId = AuthenticationHolder.getUserId();
                Boolean viewAllData = AuthenticationHolder.getUserAuth().getViewAllData();
                if (viewAllData) {
                    UserEntity userMerchant = userRelService.getUserMerchant(currentId);
                    predicates.add(criteriaBuilder.equal(root.get("userId"), userMerchant.getId()));
                } else {
                    Set<String> descendantIds = userRelService.getDescendantIds(currentId);
                    descendantIds.add(currentId);
                    predicates.add(root.get("userId").in(descendantIds));
                }
            }
            Join<OrderEntity, DeviceEntity> join = root.join("deviceEntity", JoinType.LEFT);
            if (StringUtils.isNotBlank(keyword)) {
                List<Predicate> likes = Lists.newArrayList();
                likes.add(criteriaBuilder.like(root.get("orderNo"), keyword));
                likes.add(criteriaBuilder.like(join.get("belongMerchantName"), keyword));
                likes.add(criteriaBuilder.like(join.get("washPointName"), keyword));
                likes.add(criteriaBuilder.like(root.get("customerName"), keyword));
                Predicate[] likeArr = new Predicate[likes.size()];
                predicates.add(criteriaBuilder.or(likes.toArray(likeArr)));
            }

            if (CollectionUtil.isNotEmpty(status)) {
                predicates.add(root.get("orderStatus").in(status));
            }
            if (CollectionUtil.isNotEmpty(type)) {
                if (type.contains(ProductType.WASHING_MACHINE)) {
                    predicates.add(root.get("productType").in(type));
                } else if (type.contains(ProductType.LIQUID_PUMP)) {
                    Set<ProductType> type1 = (Set<ProductType>) ((HashSet<ProductType>) type).clone();
                    type1.add(ProductType.WASHING_MACHINE);
                    type1.remove(ProductType.LIQUID_PUMP);
                    predicates.add(root.get("productType").in(type1));
                    predicates.add(root.get("command").isNotNull());
                } else {
                    predicates.add(root.get("productType").in(type));
                }
            }

            if (StringUtils.isNotBlank(startTime) && StringUtils.isNotBlank(endTime)) {
                DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
                LocalDateTime start = LocalDateTime.parse(startTime, df);
                LocalDateTime end = LocalDateTime.parse(endTime, df);
                predicates.add(criteriaBuilder.between(root.get("createTime")
                        , String.valueOf(start.toInstant(ZoneOffset.of("+8")).toEpochMilli()),
                        String.valueOf(end.toInstant(ZoneOffset.of("+8")).toEpochMilli())));
            }

            Predicate[] preArr = new Predicate[predicates.size()];
            return criteriaBuilder.and(predicates.toArray(preArr));
        };
    }

    @Override
    @Transactional
    public BaseResult<?> deviceSelfClean(String orderId) {
        OrderEntity orderEntity = orderEntityRepo.findById(orderId).orElseThrow(() -> new BusinessException(Code.NODATA, "订单不存在"));
        if (!orderEntity.getOrderStatus().equals(OrderStatus.PAID) && !orderEntity.getOrderStatus().equals(OrderStatus.SELF_CLEANING)) {
            return BaseResultFactory.produceResult(Code.ERROR, "订单状态错误");
        }
        DeviceEntity deviceEntity = deviceEntityRepo.findById(orderEntity.getDeviceId()).orElseThrow(() -> new BusinessException(Code.NODATA, "设备不存在"));
        if (Sets.newHashSet(BaseConstant.GGY8_NAME, BaseConstant.GGY13_5_NAME).contains(deviceEntity.getDeviceType())) {
            throw new BusinessException(Code.ERROR, "干衣机不支持筒自洁");
        }
        BaseResult<?> startRes = BaseResultFactory.produceEmptyResult(Code.ERROR);
        // 重试三次
        for (int i = 0; i < 3; i++) {
            switch (deviceEntity.getDeviceType()) {
                case BaseConstant.GXY8_NAME:
                case BaseConstant.GXY13_5_NAME:
                    // 滚筒洗衣
                    RollerWasherOpVO deviceControlVO = new RollerWasherOpVO();
                    deviceControlVO.setImei(deviceEntity.getImei());
                    deviceControlVO.setDeviceType(deviceEntity.getDeviceType());
                    deviceControlVO.setOrderId(orderId);
                    deviceControlVO.setWashType("5");
                    startRes = deviceOpService.wash(deviceControlVO);
                    break;
                case BaseConstant.BXY6_5_NAME:
                case BaseConstant.BXYD6_5_NAME:
                    // 波轮洗衣
                    PulseWasherOperateVO pulseWasherOperateVO = new PulseWasherOperateVO();
                    pulseWasherOperateVO.setImei(deviceEntity.getImei());
                    pulseWasherOperateVO.setDeviceType(deviceEntity.getDeviceType());
                    pulseWasherOperateVO.setOrderId(orderId);
                    pulseWasherOperateVO.setPulseNum(0);
                    pulseWasherOperateVO.setWashType(5);
                    startRes = deviceOpService.pulseWash(pulseWasherOperateVO);
                    break;
                case BaseConstant.BXX6_5_NAME:
                    // 洗鞋
                    ShoeWasherOperateVO shoeWasherOperateVO = new ShoeWasherOperateVO();
                    shoeWasherOperateVO.setImei(deviceEntity.getImei());
                    shoeWasherOperateVO.setDeviceType(deviceEntity.getDeviceType());
                    shoeWasherOperateVO.setOrderId(orderEntity.getId());
                    shoeWasherOperateVO.setPulseNum(0);
                    shoeWasherOperateVO.setWashType(5);
                    startRes = deviceOpService.shoeWash(shoeWasherOperateVO);
                    break;
            }
            if (startRes.getCode() == Code.SUCCESS.getCode()) {
                // 存储筒自洁时间
                long autoStartLeftTime = 5 * 60 * 1000 - (System.currentTimeMillis() - Long.parseLong(orderEntity.getTradeTime()));
                stringRedisTemplate.opsForValue().set(RedisKey.PAID_ORDER_DEVICE_START_LEFT_TIME_PREFIX + orderEntity.getId(), autoStartLeftTime + "");
                // 设置筒自洁
                orderEntity.setOrderStatus(OrderStatus.SELF_CLEANING);
                orderEntityRepo.save(orderEntity);
                break;
            }
        }
        return startRes;
    }
}
